第 10 章 系统
“被密封在一个仓库的纸箱中是一件计算机能做但是大多数人无法做到的事。”
——Jack Handey
在你日常使用计算机时,经常需要列出一个文件夹或者目录的内容,创建和删除文件,以及做其他一些比较无聊但是不得不做的“家务活”。在 Python 程序中可以做到同样的事,甚至能做更多的事。这些功能是否能减少你的工作量呢?我们拭目以待。
Python 在模块 os
(操作系统,operating system)中提供了许多系统函数,本章的所有程序都需要导入这个模块。
10.1 文件
和其他语言一样,Python 的文件操作很像 Unix。有些函数的名字相同,比如 chown()
和 chmod()
,不过也有很多新函数。
10.1.1 用open()
创建文件
8.1 节介绍了如何使用 open()
函数来打开文件或者创建文件。下面来创建一个名为 oops.txt 的文本文件:
>>> fout = open('oops.txt', 'wt')
>>> print('Oops, I created a file.', file=fout)
>>> fout.close()
我们用这个文件来进行一些测试。
10.1.2 用exists()
检查文件是否存在
要判断文件或者目录是否存在,可以使用 exists()
,传入相对或者绝对路径名,如下所示:
>>> import os
>>> os.path.exists('oops.txt')
True
>>> os.path.exists('./oops.txt')
True
>>> os.path.exists('waffles')
False
>>> os.path.exists('.')
True
>>> os.path.exists('..')
True
10.1.3 用isfile()
检查是否为文件
本节中的函数可以检查一个名称是文件、目录还是符号链接(详情见下方的例子)。
我们要学习的第一个函数是 isfile
,它只回答一个问题:这个是不是文件?
>>> name = 'oops.txt'
>>> os.path.isfile(name)
True
下面是判断目录的方法:
>>> os.path.isdir(name)
False
一个点号(.
)表示当前目录,两个点号(..
)表示上层目录。它们一直存在,所以下面的语句总会返回 True
:
>>> os.path.isdir('.')
True
os
模块中有许多处理路径名(完整的文件名,由 /
开始并包含所有上级目录)的函数。其中之一是 isabs()
,可以判断参数是否是一个绝对路径名。参数不需要是一个真正的文件:
>>> os.path.isabs(name)
False
>>> os.path.isabs('/big/fake/name')
True
>>> os.path.isabs('big/fake/name/without/a/leading/slash')
False
10.1.4 用copy()
复制文件
copy()
函数来自于另一个模块 shutil
。下面的例子会把文件 oops.txt 复制到文件 ohno.txt:
>>> import shutil
>>> shutil.copy('oops.txt', 'ohno.txt')
shutil.move()
函数会复制一个文件并删除原始文件。
10.1.5 用rename()
重命名文件
这个函数的作用看名字就知道了。下面的例子会把 ohno.txt 文件重命名为 ohwell.txt 文件:
>>> import os
>>> os.rename('ohno.txt', 'ohwell.txt')
10.1.6 用link()
或者symlink()
创建链接
在 Unix 中,文件只存在于一个位置,但是可以有多个名称,这种机制叫作链接。在更底层的硬链接中,找到一个文件的所有名称并不容易。可以考虑使用符号链接,它会把新名称当作文件存储,这样一次就可以获取到原始名称和新名称。link()
会创建一个硬链接。symlink()
会创建一个符号链接。islink()
函数会检查参数是文件还是符号链接。
下面这个例子展示了如何把已有文件 oops.txt 硬链接到一个新文件 yikes.txt:
>>> os.link('oops.txt', 'yikes.txt')
>>> os.path.isfile('yikes.txt')
True
下面的例子展示了如何把已有文件 oops.txt 符号链接到一个新文件 jeepers.txt:
>>> os.path.islink('yikes.txt')
False
>>> os.symlink('oops.txt', 'jeepers.txt')
>>> os.path.islink('jeepers.txt')
True
10.1.7 用chmod()
修改权限
在 Unix 系统中,chmod()
可以修改文件权限。可以设置用户(如果是你创建了文件,那用户通常就是你)、当前用户所在用户组以及其他所有用户组的读、写和执行权限。这个命令接收一个压缩过的八进制(基数为 8)值,这个值包含用户、用户组和权限。举例来说,下面的命令可以让 oops.txt 只能被拥有者读:
>>> os.chmod('oops.txt', 0o400)
如果你不想用压缩过的八进制值并且更喜欢那些(有点)难懂的符号,可以从 stat
模块中导入一些常量并用在语句中:
>>> import stat
>>> os.chmod('oops.txt', stat.S_IRUSR
10.1.8 用chown()
修改所有者
这个函数也是 Unix/Linux/Mac 特有的。你可以指定用户的 ID(uid)和用户组 ID(gid)来修改文件的所有者和 / 或所有用户组:
>>> uid = 5
>>> gid = 22
>>> os.chown('oops', uid, gid)
10.1.9 用abspath()
获取路径名
这个函数会把一个相对路径名扩展成绝对路径名。如果你的当前目录是 usrgabenlunzie,其中有文件 oops.txt,那你可以用这个函数得到下面的输出:
>>> os.path.abspath('oops.txt')
'usrgaberlunzie/oops.txt'
10.1.10 用realpath()
获取符号的路径名
在之前的几节中,我们创建了一个 oops.txt 到 jeepers.txt 的符号链接。在这种情况下,你可以使用 realpath()
函数从 jeepers.txt 获取名称 oops.txt,如下所示:
>>> os.path.realpath('jeepers.txt')
'usrgaberlunzie/oops.txt'
10.1.11 用remove()
删除文件
下面的例子使用 remove()
函数来删除 oops.txt 文件:
>>> os.remove('oops.txt')
>>> os.path.exists('oops.txt')
False
10.2 目录
在大多数操作系统中,文件被存储在多级目录(现在经常被称为文件夹)中。包含所有这些文件和目录的容器是文件系统(有时候被称为卷)。标准模块 os
可以处理这些东西,下面是一些可以使用的函数。
10.2.1 使用mkdir()
创建目录
下面的例子展示了如何创建目录 poems
:
>>> os.mkdir('poems')
>>> os.path.exists('poems')
True
10.2.2 使用rmdir()
删除目录
假如你现在发现不需要这个目录,可以用这个函数来删除目录:
>>> os.rmdir('poems')
>>> os.path.exists('poems')
False
10.2.3 使用listdir()
列出目录内容
你又后悔了,来重新创建 poems
并加入一些内容:
>>> os.mkdir('poems')
现在列出它的内容(目前还是空):
>>> os.listdir('poems')
[]
接着创建一个子目录:
>>> os.mkdir('poems/mcintyre')
>>> os.listdir('poems')
['mcintyre']
在这个子目录中,创建一个文件(不要手动输入这些文字,除非你真的很喜欢这首诗;一定要确保文中的单引号和三引号可以正确匹配):
>>> fout = open('poems/mcintyre/the_good_man', 'wt')
>>> fout.write('''Cheerful and happy was his mood,
... He to the poor was kind and good,
... And he oft' times did find them food,
... Also supplies of coal and wood,
... He never spake a word was rude,
... And cheer'd those did o'er sorrows brood,
... He passed away not understood,
... Because no poet in his lays
... Had penned a sonnet in his praise,
... 'Tis sad, but such is world's ways.
... ''')
344
>>> fout.close()
最后来看看目录中有什么:
>>> os.listdir('poems/mcintyre')
['the_good_man']
10.2.4 使用chdir()
修改当前目录
你可以使用这个函数从一个目录跳转到另一个目录。我们从当前目录跳转到 poems
目录中:
>>> import os
>>> os.chdir('poems')
>>> os.listdir('.')
['mcintyre']
10.2.5 使用glob()
列出匹配文件
glob()
函数会使用 Unix shell 的规则来匹配文件或者目录,而不是更复杂的正则表达式。具体规则如下所示:
*
会匹配任意名称(re
中是.*
)?
会匹配一个字符[abc]
会匹配字符a
、b
和c
[!abc]
会匹配除了a
、b
和c
之外的所有字符
试着获取所有以 m
开头的文件和目录:
>>> import glob
>>> glob.glob('m*')
['mcintyre']
获取所有名称为两个字符的文件和目录:
>>> glob.glob('??')
[]
获取名称为 8 个字符并且以 m
开头和以 e
结尾的文件和目录:
>>> glob.glob('m??????e')
['mcintyre']
获取所有以 k
、l
或者 m
开头并且以 e
结尾的文件和目录:
>>> glob.glob('[klm]*e')
['mcintyre']
10.3 程序和进程
当运行一个程序时,操作系统会创建一个进程。它会使用系统资源(CPU、内存和磁盘空间)和操作系统内核中的数据结构(文件、网络连接、用量统计等)。进程之间是互相隔离的,即一个进程既无法访问其他进程的内容,也无法操作其他进程。
操作系统会跟踪所有正在运行的进程,给每个进程一小段运行时间,然后切换到其他进程,这样既可以做到公平又可以响应用户操作。你可以在图形界面中查看进程状态,在 Mac OS X 上可以使用活动监视器,在 Windows 上可以使用任务管理器。
你也可以自己编写程序来获取进程信息。标准库模块 os
提供了一些常用的获取系统信息的函数。举例来说,下面的函数会获取正在运行的 Python 解释器的进程号和当前工作目录:
>>> import os
>>> os.getpid()
76051
>>> os.getcwd()
'Userswilliamlubanovic'
下面的函数会获取我的用户 ID 和用户组 ID:
>>> os.getuid()
501
>>> os.getgid()
20
10.3.1 使用subprocess
创建进程
到目前为止,你看到的所有程序都是单进程程序。你可以使用 Python 标准库中的 subprocess
模块来启动和终止其他程序。如果只是想在 shell 中运行其他程序并获取它的输出(标准输出和标准错误输出),可以使用 getoutput()
函数。这里获取了 Unix date
程序的输出:
>>> import subprocess
>>> ret = subprocess.getoutput('date')
>>> ret
'Sun Mar 30 22:54:37 CDT 2014'
在进程执行完毕之前,你获取不到任何内容。如果需要调用一些比较耗时的程序,可以使用 11.1 节提到的并发。因为 getoutput()
的参数是一个字符串,可以表示一个完整的 shell 命令,所以你可以在里面使用参数、管道、I/O 重定向 <
和 >
,等等:
>>> ret = subprocess.getoutput('date -u')
>>> ret
'Mon Mar 31 03:55:01 UTC 2014'
把这个输出用管道传给 wc
命令,可以计算出一共有 1 行、6 个单词和 29 个字符:
>>> ret = subprocess.getoutput('date -u | wc')
>>> ret
' 1 6 29'
另一个类似的方法是 check_output()
,可以接受一个命令和参数列表。默认情况下,它返回的不是字符串,而是字节类型的标准输出。此外,这个函数并没有使用 shell:
>>> ret = subprocess.check_output(['date', '-u'])
>>> ret
b'Mon Mar 31 04:01:50 UTC 2014\n'
如果要获取其他程序的退出状态,可以使用 getstatusoutput()
函数,它会返回一个包含状态码和输出的元组:
>>> ret = subprocess.getstatusoutput('date')
>>> ret
(0, 'Sat Jan 18 21:36:23 CST 2014')
如果只想要退出状态,可以使用 call()
:
>>> ret = subprocess.call('date')
Sat Jan 18 21:33:11 CST 2014
>>> ret
0
(在 Unix 类操作系统中,退出状态 0
通常表示运行成功。)
本例中,日期和时间被打印到输出中,但是并没有被我们的程序获取,所以 ret
中只有状态码。
你可以用两种方式来运行带参数的程序。第一种是在字符串中写明参数。我们的示例中使用的是 date -u
,这会打印出 UTC 格式的当前日期和时间(稍后会有 UTC 的解释):
>>> ret = subprocess.call('date -u', shell=True)
Tue Jan 21 04:40:04 UTC 2014
需要加上参数 shell=True
,这样函数就会用 shell 来执行命令,shell 会把 date -u
分割成单独的字符串并对通配符(比如 *
,我们的例子中没有使用它)进行扩展。
第二种方式是传入一个参数列表,这样函数就不需要调用 shell:
>>> ret = subprocess.call(['date', '-u'])
Tue Jan 21 04:41:59 UTC 2014
10.3.2 使用multiprocessing
创建进程
你可以在一个单独的进程中运行一个 Python 函数,也可以使用 multiprocessing
模块在一个程序中运行多个进程。下面的例子做了一些很无聊的事,把它存储为 mp.py,然后用 python mp.py
运行它:
import multiprocessing
import os
def do_this(what):
whoami(what)
def whoami(what):
print("Process %s says: %s" % (os.getpid(), what))
if __name__ == "__main__":
whoami("I'm the main program")
for n in range(4):
p = multiprocessing.Process(target=do_this,
args=("I'm function %s" % n,))
p.start()
运行时得到如下输出:
Process 6224 says: I'm the main program
Process 6225 says: I'm function 0
Process 6226 says: I'm function 1
Process 6227 says: I'm function 2
Process 6228 says: I'm function 3
Process()
函数会创建一个新进程来运行 do_this()
函数。由于我们在一个循环中执行它,所以生成了 4 个执行 do_this()
的进程并在执行完毕后退出。
multiprocessing
模块真正的功能比这个例子中所展示的要强大得多。当你需要用多进程来减少运行时间时,它非常有用,比如下载需要抓取的网页、调整图片尺寸等。它支持任务队列和进程间通信,而且可以等待所有进程执行完毕。11.1 节会详细讲解。
10.3.3 使用terminate()
终止进程
如果创建了一个或者多个进程并且想终止它们(可能是因为进入了死循环,也可能是因为你觉得很无聊,还有可能是因为你只是想干坏事),可以使用 terminate()
。下面的例子中,我们的进程会一直计数到百万,每次计数之后都会等待 1 秒并打印出相关信息。然而,主程序只会保持 5 秒耐心,之后就会终止进程:
import multiprocessing
import time
import os
def whoami(name):
print("I'm %s, in process %s" % (name, os.getpid()))
def loopy(name):
whoami(name)
start = 1
stop = 1000000
for num in range(start, stop):
print("\tNumber %s of %s. Honk!" % (num, stop))
time.sleep(1)
if __name__ == "__main__":
whoami("main")
p = multiprocessing.Process(target=loopy, args=("loopy",))
p.start()
time.sleep(5)
p.terminate()
运行程序时得到如下输出:
I'm main, in process 97080
I'm loopy, in process 97081
Number 1 of 1000000. Honk!
Number 2 of 1000000. Honk!
Number 3 of 1000000. Honk!
Number 4 of 1000000. Honk!
Number 5 of 1000000. Honk!
10.4 日期和时间
程序员们需要花费大量时间来处理日期和时间。我们会讨论一些常见的问题,之后会介绍一些对应的最佳实践和能够帮助缓解问题的小技巧。
可以用多种方式来表示日期,甚至多到让人厌烦。即使是使用罗马历的英语国家也有很多表示日期的方法:
July 29 1984
29 Jul 1984
29/7/1984
7/29/1984
表示日期的第一个问题就是二义性。从上面的例子可以很容易看出,7 表示月份,29 表示天数,因为月份不可能是 29。但是 1/6/2012
呢?它到底是 1 月 6 日还是 6 月 1 日呢?
罗马历中,月份的名字在不同语言中差别很大。在其他文化中,年份和月份本身的定义都有可能不一样。
闰年是另一个问题。你可能知道,每隔 4 年会出现一个闰年(夏季奥运会和美国总统选举也是隔四年一次)。不过你知道吗,年份是整百数时,必须是 400 的倍数才是闰年。下面的代码可以检测是否是闰年:
>>> import calendar
>>> calendar.isleap(1900)
False
>>> calendar.isleap(1996)
True
>>> calendar.isleap(1999)
False
>>> calendar.isleap(2000)
True
>>> calendar.isleap(2002)
False
>>> calendar.isleap(2004)
True
时间则有另外的问题,主要是因为时区以及夏时制。观察时区图会发现,时区是按照政治和历史因素分割的,并不是按照经度 15 度(360 度 /24)分割。不同国家每年夏时制的开始和结束时间也不一样。实际上,南半球和北半球的夏时制时间段刚好相反(细想就知道原因了)。
Python 的标准库中有很多日期和时间模块:datetime
、time
、calendar
、dateutil
,等等。有些在功能上有重复,还有点不好理解。
10.4.1 datetime
模块
首先介绍一下标准 datetime
模块。它定义了 4 个主要的对象,每个对象都有很多方法:
date
处理年、月、日time
处理时、分、秒和微秒datetime
处理日期和时间同时出现的情况timedelta
处理日期和 / 或时间间隔
你可以指定年、月、日,来创建一个 date
对象。这些值之后会变成对象的属性:
>>> from datetime import date
>>> halloween = date(2014, 10, 31)
>>> halloween
datetime.date(2014, 10, 31)
>>> halloween.day
31
>>> halloween.month
10
>>> halloween.year
2014
可以使用 isoformat()
方法打印一个 date
对象:
>>> halloween.isoformat()
'2014-10-31'
iso
是指 ISO 8601,一种表示日期和时间的国际标准。这个标准的显示顺序是从一般(年)到特殊(日)。它也可以对日期进行正确的排序:先按照年,然后是月,最后是日。我经常在程序中使用这种格式表示日期,还会在存储和日期相关的数据时当作文件名。下一节会介绍更复杂的 strptime()
和 strftime()
方法,它们分别用来解析和格式化日期。
下面的例子使用 today()
方法生成今天的日期:
>>> from datetime import date
>>> now = date.today()
>>> now
datetime.date(2014, 2, 2)
下面的例子使用 timedelta
对象来实现 date
的加法:
>>> from datetime import timedelta
>>> one_day = timedelta(days=1)
>>> tomorrow = now + one_day
>>> tomorrow
datetime.date(2014, 2, 3)
>>> now + 17*one_day
datetime.date(2014, 2, 19)
>>> yesterday = now - one_day
>>> yesterday
datetime.date(2014, 2, 1)
date
的范围 是 date.min
( 年 = 1, 月 = 1, 日 = 1) 到 date.max
( 年 = 9999, 月 = 12, 日 = 31)。因此,不能使用它来进行和历史或者天文相关的计算。
datetime
模块中的 time
对象用来表示一天中的时间:
>>> from datetime import time
>>> noon = time(12, 0, 0)
>>> noon
datetime.time(12, 0)
>>> noon.hour
12
>>> noon.minute
0
>>> noon.second
0
>>> noon.microsecond
0
参数的顺序是按照时间单位从大(时)到小(微秒)排列。如果没有参数,time
会默认全部使用 0。顺便说一句,能够存取微秒并不意味着你能从计算机中得到准确的微秒。每秒的准确度取决于硬件和操作系统中的很多因素。
datetime
对象既包含日期也包含时间。你可以直接创建一个 datetime
对象,如下所示,表示 2014 年 1 月 2 日上午 3:04,5 秒 6 微秒:
>>> from datetime import datetime
>>> some_day = datetime(2014, 1, 2, 3, 4, 5, 6)
>>> some_day
datetime.datetime(2014, 1, 2, 3, 4, 5, 6)
datetime
对象也有一个 isoformat()
方法:
>>> some_day.isoformat()
'2014-01-02T03:04:05.000006'
中间的 T
把日期和时间分割开。
datetime
有一个 now()
方法,可以用它获取当前日期和时间:
>>> from datetime import datetime
>>> now = datetime.now()
>>> now
datetime.datetime(2014, 2, 2, 23, 15, 34, 694988)
14
>>> now.month
2
>>> now.day
2
>>> now.hour
23
>>> now.minute
15
>>> now.second
34
>>> now.microsecond
694988
可以使用 combine()
方法把一个 date
对象和一个 time
对象合并成一个 datetime
对象:
>>> from datetime import datetime, time, date
>>> noon = time(12)
>>> this_day = date.today()
>>> noon_today = datetime.combine(this_day, noon)
>>> noon_today
datetime.datetime(2014, 2, 2, 12, 0)
也可以使用 date()
和 time()
方法从 datetime
中取出 date
和 time
部分:
>>> noon_today.date()
datetime.date(2014, 2, 2)
>>> noon_today.time()
datetime.time(12, 0)
10.4.2 使用time
模块
Python 的 datetime
模块中有一个 time
对象,Python 还有一个单独的 time
模块,这有点令人困扰。此外,time
模块还有一个函数叫作 time()
。
一种表示绝对时间的方法是计算从某个起始点开始的秒数。Unix 时间使用的是从 1970 年 1 月 1 日 0 点开始的秒数 1。这个值通常被称为纪元,它是不同系统之间最简单的交换日期时间的方法。
1开始时间大约与 Unix 出现的时间吻合。
time
模块的 time()
函数会返回当前时间的纪元值:
>>> import time
>>> now = time.time()
>>> now
1391488263.664645
数一下位数,从 1970 年新年 2 到现在已经过去了超过十亿秒。时间都去哪儿了?
2美国新年是中国的元旦。——译者注
可以用 ctime()
把一个纪元值转换成一个字符串:
>>> time.ctime(now)
'Mon Feb 3 22:31:03 2014'
下一节会介绍如何生成其他格式的日期和时间。
纪元值是不同系统(比如 JavaScript)之间交换日期和时间的一种非常有用的方法。但是,有时候想要真正的日期而不是一串数字,这时可以使用 struct_time
对象。localtime()
会返回当前系统时区下的时间,gmtime()
会返回 UTC 时间:
>>> time.localtime(now)
time.struct_time(tm_year=2014, tm_mon=2, tm_mday=3, tm_hour=22, tm_min=31,
tm_sec=3, tm_wday=0, tm_yday=34, tm_isdst=0)
>>> time.gmtime(now)
time.struct_time(tm_year=2014, tm_mon=2, tm_mday=4, tm_hour=4, tm_min=31,
tm_sec=3, tm_wday=1, tm_yday=35, tm_isdst=0)
在我所处的时区(中央时区),22:31 是 UTC(正式称呼是格林威治时间或者祖鲁时间)第二天的 04:31。如果省略 localtime()
或者 gmtime()
的参数,默认会返回当前时间。
对应上面两个函数的是 mktime()
,它会把 struct_time
对象转换回纪元值:
>>> tm = time.localtime(now)
>>> time.mktime(tm)
1391488263.0
这个值和之前 now()
返回的纪元值并不完全相同,因为 struct_time
对象只能精确到秒。
一些建议:尽量多使用 UTC 来代替时区。UTC 是绝对时间,和时区无关。如果你有服务器,把它的时间设置为 UTC,不要使用本地时间。
还有一些建议(都是免费的):如果有可能,绝对不使用夏时制时间。如果使用了夏时制时间,那每年的一段时间(春季)会少一个小时,另一段时间(秋季)会多一个小时。出于某些原因,许多组织在它们的计算机系统中使用夏时制,但是每年都需要处理数据重复和丢失。说起来都是泪。
别忘了,UTC 指时间,UTF-8 指字符串(更多关于 UTF-8 的内容参见第 7 章)。
10.4.3 读写日期和时间
isoformat()
并不是唯一一种可以打印日期和时间的方法。前面已经见过,time
模块中的 ctime()
函数可以把纪元转换成字符串:
>>> import time
>>> now = time.time()
>>> time.ctime(now)
'Mon Feb 3 21:14:36 2014'
也可以使用 strftime()
把日期和时间转换成字符串。这个方法在 datetime
、date
和 time
对象中都有,在 time
模块中也有。strftime()
使用格式化字符串来指定输出,详见表 10-1。
表10-1:strftime()
的格式化字符串
格式化字符串 | 日期/时间单元 | 范围 |
---|---|---|
%Y
| 年 |
1900-…
|
%m
| 月 |
01-12
|
%B
| 月名 |
January,…
|
%b
| 月名缩写 |
Jan,…
|
%d
| 日 |
01-31
|
%A
| 星期 |
Sunday,…
|
%a
| 星期缩写 |
Sun,…
|
%H
| 时(24 小时制) |
00-23
|
%I
| 时(12 小时制) |
01-12
|
%p
| 上午 / 下午 |
AM, PM
|
%M
| 分 |
00-59
|
%S
| 秒 |
00-59
|
数字都是左侧补零。
下面是 time
模块中的 strftime()
函数。它会把 struct_time
对象转换成字符串。我们首先定义格式化字符串 fmt
,然后使用它:
>>> import time
>>> fmt = "It's %A, %B %d, %Y, local time %I:%M:%S%p"
>>> t = time.localtime()
>>> t
time.struct_time(tm_year=2014, tm_mon=2, tm_mday=4, tm_hour=19,
tm_min=28, tm_sec=38, tm_wday=1, tm_yday=35, tm_isdst=0)
>>> time.strftime(fmt, t)
"It's Tuesday, February 04, 2014, local time 07:28:38PM"
如果使用 date
对象的 strftime()
函数,只能获取日期部分,时间默认是午夜:
>>> from datetime import date
>>> some_day = date(2014, 7, 4)
>>> fmt = "It's %B %d, %Y, local time %I:%M:%S%p"
>>> some_day.strftime(fmt)
"It's Friday, July 04, 2014, local time 12:00:00AM"
对于 time
对象,只会转换时间部分:
>>> from datetime import time
>>> some_time = time(10, 35)
>>> some_time.strftime(fmt)
"It's Monday, January 01, 1900, local time 10:35:00AM"
显然,你肯定不想使用 time
对象的日期部分,因为它们毫无意义。
如果要把字符串转换成日期或者时间,可以对同样的格式化字符串使用 strptime()
函数。
这里不能使用正则表达式,字符串的非格式化部分(没有 %
的部分)必须完全匹配。假设可以匹配格式“年 - 月 - 日”,比如 2012-01-29
,如果目标字符串中用空格代替破折号,解析时会发生什么呢?
>>> import time
>>> fmt = "%Y-%m-%d"
>>> time.strptime("2012 01 29", fmt)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "LibraryFrameworks/Python.framework/Versions/3.3/lib/
python3.3/_strptime.py", line 494, in strptimetime
tt = strptime(datastring, format)[0]
File "LibraryFrameworks/Python.framework/Versions/3.3/lib/
python3.3/_strptime.py", line 337, in strptime
(datastring, format))
ValueError: time data '2012 01 29' does not match format '%Y-%m-%d'
如果传入 strptime()
的字符串中有破折号会怎么样呢?
>>> time.strptime("2012-01-29", fmt)
time.struct_time(tm_year=2012, tm_mon=1, tm_mday=29, tm_hour=0, tm_min=0,
tm_sec=0, tm_wday=6, tm_yday=29, tm_isdst=-1)
现在可以正常解析了。
即使字符串看起来可以匹配格式,但如果超出取值范围也会报错:
>>> time.strptime("2012-13-29", fmt)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "LibraryFrameworks/Python.framework/Versions/3.3/lib/
python3.3/_strptime.py", line 494, in strptimetime
tt = strptime(datastring, format)[0]
File "LibraryFrameworks/Python.framework/Versions/3.3/lib/
python3.3/_strptime.py", line 337, in strptime
(datastring, format))
ValueError: time data '2012-13-29' does not match format '%Y-%m-%d'
名称通过你的 locale 设置,这是操作系统中的国际化设置。如果要打印出不同的月和日名称,可以通过 setlocale()
来修改。这个函数的第一个参数是 locale.LC_TIME
,表示设置的是日期和时间,第二个参数是一个结合了语言和国家名称的缩写字符串。假设我们邀请了一些国际友人来参加万圣节派对,需要分别打印出美国英语、法语、德语、西班牙语和冰岛语中的月、日和具体的星期。(什么?你觉得冰岛人并不喜欢这样的派对?错!他们甚至有真正的精灵。)
>>> import locale
>>> from datetime import date
>>> halloween = date(2014, 10, 31)
>>> for lang_country in ['en_us', 'fr_fr', 'de_de', 'es_es', 'is_is',]:
... locale.setlocale(locale.LC_TIME, lang_country)
... halloween.strftime('%A, %B %d')
...
'en_us'
'Friday, October 31'
'fr_fr'
'Vendredi, octobre 31'
'de_de'
'Freitag, Oktober 31'
'es_es'
'viernes, octubre 31'
'is_is'
'föstudagur, október 31'
>>>
去哪儿找这些神奇的 lang_country
值呢?虽然不是特别靠谱,不过可以用下面的方式来获取所有值(有好几百种):
>>> import locale
>>> names = locale.locale_alias.keys()
我们从 names
中过滤出可以用在 setlocale()
中的名称,它们的格式和之前例子中的类似,两个字符构成的语言代码(http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)加一个下划线和两个字符构成的国家代码(http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2):
>>> good_names = [name for name in names if \
len(name) == 5 and name[2] == '_']
看看前 5 个是什么?
>>> good_names[:5]
['sr_cs', 'de_at', 'nl_nl', 'es_ni', 'sp_yu']
如果想获取所有的德国语言,试试这个:
>>> de = [name for name in good_names if name.startswith('de')]
>>> de
['de_at', 'de_de', 'de_ch', 'de_lu', 'de_be']
10.4.4 其他模块
如果你觉得标准库模块不好用或者没有你想要的功能,可以试试下面这些第三方模块。
arrow
(http://crsmithdev.com/arrow/)
这个模块有许多日期和时间函数,并提供了简单易用的 API。
dateutil
(http://labix.org/python-dateutil)
这个模块可以解析绝大多数日期格式,并且可以很好地处理相对日期和时间。
iso8601
(https://pypi.python.org/pypi/iso8601)
这个模块会完善标准库中对于 ISO8601 格式的处理。
这个模块提供了许多时区函数。
10.5 练习
(1) 把当前日期以字符串形式写入文本文件 today.txt。
(2) 从 today.txt 中读取字符串到 today_string
中。
(3) 从 today_string
中解析日期。
(4) 列出当前目录下的文件。
(5) 列出父目录下的文件。
(6) 使用 multiprocessing
创建三个进程,让它们等待随机的秒数(范围 1~5),打印出当前时间并退出。
(7) 用你的生日创建一个 date
对象。
(8) 你的生日是星期几?
(9) 你出生 10 000 天的日期是什么?