您在这里: 主页 ‣ 深入Python 3 ‣
难度等级: ♦♦♢♢♢
❝ Our imagination is stretched to the utmost, not, as in fiction, to imagine things which are not really there, but just to comprehend those things which are. ❞
— Richard Feynman
这一章节将围绕一个非常强大的技术向你介绍列表解析,字典解析和集合解析这三个概念。但是,我要先打个岔介绍两个帮助你浏览本地文件系统的模块。
⁂
Python 3 带有一个模块叫做 os
,代表 “操作系统(operating system)。” os
模块 包含非常多的函数用于获取(和修改)本地目录、文件进程、环境变量等的信息。Python 尽最大的努力在所有支持的操作系统上提供一个统一的API, 这样你就可以在保证程序能够在任何的计算机上运行的同时尽量少的包含平台特定的代码。
当你刚刚开始学习Python的时候, 你将花大量的时间在 Python Shell上。 在整本书中,你将一直看见类似下面的例子:
examples
目录导入某一个模块
如果你不知道当前工作目录, 第一步很可能会得到一个ImportError
。 为什么? 因为 Python 将在导入搜索路径中查找示例模块, 但是由于examples
目录没有包含在搜索路径中,查找将失败。 你可以通过下面两个方法之一来解决这个问题:
examples
目录加入到导入搜索路径中
examples
目录
Python在任何时候都在暗地里记住了当前工作目录这个属性。无论你是在Python Shell 中,还是在命令行运行你自己的Python 脚本,抑或是在Web 服务器上运行Python CGI 脚本,当前工作目录总是存在。
os
模块提供了两个函数处理当前工作目录
>>> import os ① >>> print(os.getcwd()) ② C:\Python31 >>> os.chdir('/Users/pilgrim/diveintopython3/examples') ③ >>> print(os.getcwd()) ④ C:\Users\pilgrim\diveintopython3\examples
os
是Python 自带的; 你可以在任何时间,任何地方导入它。
os.getcwd()
函数获得当前工作目录。当你运行一个图形化的Python Shell 时,当前工作目录默认将是Python Shell的可执行文件所在的目录。在Windows 上, 这个目录取决于你将Python安装在哪里; 默认位置是 c:\Python31
。如果你通过命令行运行Python Shell,当前工作目录是你运行python3
时所在的目录。
os.chdir()
函数改变当前工作目录
os.chdir()
函数时,即使在Windows上,我也总是使用Linux风格的路径(正斜杠,没有盘符)。这就是Python 尝试隐藏操作系统差异的一个地方。
既然我们说到了目录,我得指出 os.path
模块。os.path
模块包含了操作文件名和目录名的函数.
>>> import os >>> print(os.path.join('/Users/pilgrim/diveintopython3/examples/', 'humansize.py')) ① /Users/pilgrim/diveintopython3/examples/humansize.py >>> print(os.path.join('/Users/pilgrim/diveintopython3/examples', 'humansize.py')) ② /Users/pilgrim/diveintopython3/examples\humansize.py >>> print(os.path.expanduser('~')) ③ c:\Users\pilgrim >>> print(os.path.join(os.path.expanduser('~'), 'diveintopython3', 'examples', 'humansize.py')) ④ c:\Users\pilgrim\diveintopython3\examples\humansize.py
os.path.join()
函数从一个或多个路径片段中构造一个路径名。 在这个例子中, 它仅仅是简单的拼接字符串.
join
函数给路径名添加一个额外的斜杠。由于我在Windows 上写这个例子, 这个斜杠是一个反斜杠而不是正斜杠。如果你在Linux 或者Mac OS X上重现这个例子, 你将会看见正斜杠. 无论你使用哪种形式的斜杠,Python 都可以访问到文件。
os.path.expanduser()
用来将包含~
符号(表示当前用户Home目录)的路径扩展为完整的路径。在任何有Home 目录概念的操作系统上(包括Linux,Mac OS X 和Windows),这个函数都能工作。返回的路径不以斜杠结尾,但是os.path.join()
并不介意这一点。
os.path.join()
可以接受任何数量的参数。当我发现这一点时我大喜过望, 因为在一门新的语言中构造我的工具箱时,addSlashIfNecessary()
总是我不得不写的愚蠢的小函数之一。不要 在Python 中写这个愚蠢的小函数,聪明的人们已经帮你考虑过这个问题了。
os.path
也包含用于分割完整路径名,目录名和文件名的函数
>>> pathname = '/Users/pilgrim/diveintopython3/examples/humansize.py' >>> os.path.split(pathname) ① ('/Users/pilgrim/diveintopython3/examples', 'humansize.py') >>> (dirname, filename) = os.path.split(pathname) ② >>> dirname ③ '/Users/pilgrim/diveintopython3/examples' >>> filename ④ 'humansize.py' >>> (shortname, extension) = os.path.splitext(filename) ⑤ >>> shortname 'humansize' >>> extension '.py'
split
函数分割一个完整路径并返回目录和文件名。
os.path.split()
函数正是这样做的。 将split
函数的返回值赋值给一个二元组。每个变量获得了返回元组中的对应元素的值。
os.path.split()
函数返回元组中的第一个元素,文件所在的目录。
os.path.split()
函数返回元组中的第二个元素,文件名。
os.path
也包含os.path.splitext()
函数,它分割一个文件名并返回短文件名和扩展名。可以使用同样的技术将它们的值赋值给不同的变量。
glob
模块是Python标准库中的另一个工具,它可以通过编程的方法获得一个目录的内容,并且它使用熟悉的命令行下的通配符。
>>> os.chdir('/Users/pilgrim/diveintopython3/') >>> import glob >>> glob.glob('examples/*.xml') ① ['examples\\feed-broken.xml', 'examples\\feed-ns0.xml', 'examples\\feed.xml'] >>> os.chdir('examples/') ② >>> glob.glob('*test*.py') ③ ['alphameticstest.py', 'pluraltest1.py', 'pluraltest2.py', 'pluraltest3.py', 'pluraltest4.py', 'pluraltest5.py', 'pluraltest6.py', 'romantest1.py', 'romantest10.py', 'romantest2.py', 'romantest3.py', 'romantest4.py', 'romantest5.py', 'romantest6.py', 'romantest7.py', 'romantest8.py', 'romantest9.py']
glob
模块接受一个通配符并返回所有匹配的文件和目录的路径。在这个例子中,通配符是一个目录名加上 “*.xml
”, 它匹配examples
子目录下的所有.xml
文件。
examples
目录。 os.chdir()
可以接受相对路径.
.py
并且在文件名中包含单词test
的文件。
每一个现代文件系统都对文件存储了元信息: 创建时间,最后修改时间,文件大小等等。Python 单独提供了一个的API 用于访问这些元信息。 你不需要打开文件。知道文件名就足够了。
>>> import os >>> print(os.getcwd()) ① c:\Users\pilgrim\diveintopython3\examples >>> metadata = os.stat('feed.xml') ② >>> metadata.st_mtime ③ 1247520344.9537716 >>> import time ④ >>> time.localtime(metadata.st_mtime) ⑤ time.struct_time(tm_year=2009, tm_mon=7, tm_mday=13, tm_hour=17, tm_min=25, tm_sec=44, tm_wday=0, tm_yday=194, tm_isdst=1)
examples
文件夹。
feed.xml
是examples
文件夹中的一个文件。 调用os.stat()
函数返回一个包含多种文件元信息的对象。
st_mtime
是最后修改时间,它的格式不是很有用。(技术上讲,它是从纪元,也就是1970年1月1号的第一秒钟,到现在的秒数)
time
模块是Python标准库的一部分。 它包含用于在不同时间格式中转换,将时间格式化成字符串以及处理时区的函数。
time.localtime()
函数将从纪元到现在的秒数这个格式表示的时间(os.stat()
函数返回值的st_mtime
属性)转换成更有用的包含年、月、日、小时、分钟、秒的结构体。这个文件的最后修改时间是2009年7月13日下午5:25。
# continued from the previous example >>> metadata.st_size ① 3070 >>> import humansize >>> humansize.approximate_size(metadata.st_size) ② '3.0 KiB'
os.stat()
函数也通过st_size
属性返回文件大小。文件feed.xml
的大小是 3070
字节。
st_size
属性作为参数传给approximate_size()
函数。
在前一节中,glob.glob()
函数返回一个相对路径的列表。第一个例子的路径类似'examples\feed.xml'
,而第二个例子的路径'romantest1.py'
更短。只要你保持在当前工作目录中,你就可以使用这些相对路径来打开文件或者获得文件的元信息。但是当你希望构造一个从根目录开始或者是包含盘符的绝对路径时,你就需要用到os.path.realpath()
函数了。
>>> import os >>> print(os.getcwd()) c:\Users\pilgrim\diveintopython3\examples >>> print(os.path.realpath('feed.xml')) c:\Users\pilgrim\diveintopython3\examples\feed.xml
⁂
列表解析提供了一种紧凑的方式,实现了通过对列表中每一个元素应用一个函数的方法来将一个列表映射到另一个列表.
>>> a_list = [1, 9, 8, 4] >>> [elem * 2 for elem in a_list] ① [2, 18, 16, 8] >>> a_list ② [1, 9, 8, 4] >>> a_list = [elem * 2 for elem in a_list] ③ >>> a_list [2, 18, 16, 8]
elem * 2
并且将结果添加到返回列表中。
你可以在列表解析中使用任何的Python表达式, 包括os
模块中用于操作文件和目录的函数。
>>> import os, glob >>> glob.glob('*.xml') ① ['feed-broken.xml', 'feed-ns0.xml', 'feed.xml'] >>> [os.path.realpath(f) for f in glob.glob('*.xml')] ② ['c:\\Users\\pilgrim\\diveintopython3\\examples\\feed-broken.xml', 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed-ns0.xml', 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed.xml']
.xml
文件。
.xml
文件列表并将其转化成全路径的列表。
列表解析也可以过滤列表,生成比原列表短的结果列表。
>>> import os, glob
>>> [f for f in glob.glob('*.py') if os.stat(f).st_size > 6000] ①
['pluraltest6.py',
'romantest10.py',
'romantest6.py',
'romantest7.py',
'romantest8.py',
'romantest9.py']
if
子句来过滤列表。对于列表中每一个元素if
关键字后面的表达式都会被计算。如果表达式的计算结果为True
,那么这个元素将会被包含在输出中。这个列表解析在当前目录查找所有.py
文件,而 if
表达式通过测试文件大小是否大于6000
字节对列表进行过滤。有6个符合条件的文件,所以这个列表解析返回包含六个文件名的列表。
到目前为止的例子中的列表解析都只是用了一些简单的表达式, 乘以一个常数、调用一个函数或者是在过滤后返回原始元素。 然而列表解析并不限制表达式的复杂程度。
>>> import os, glob >>> [(os.stat(f).st_size, os.path.realpath(f)) for f in glob.glob('*.xml')] ① [(3074, 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed-broken.xml'), (3386, 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed-ns0.xml'), (3070, 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed.xml')] >>> import humansize >>> [(humansize.approximate_size(os.stat(f).st_size), f) for f in glob.glob('*.xml')] ② [('3.0 KiB', 'feed-broken.xml'), ('3.3 KiB', 'feed-ns0.xml'), ('3.0 KiB', 'feed.xml')]
.xml
文件, 对于每一个文件构造一个包含文件大小(通过调用os.stat()
获得)和绝对路径(通过调用os.path.realpath()
)的元组。
.xml
文件的大小应用approximate_size()
函数。
⁂
字典解析和列表解析类似,只不过它生成字典而不是列表。
>>> import os, glob >>> metadata = [(f, os.stat(f)) for f in glob.glob('*test*.py')] ① >>> metadata[0] ② ('alphameticstest.py', nt.stat_result(st_mode=33206, st_ino=0, st_dev=0, st_nlink=0, st_uid=0, st_gid=0, st_size=2509, st_atime=1247520344, st_mtime=1247520344, st_ctime=1247520344)) >>> metadata_dict = {f:os.stat(f) for f in glob.glob('*test*.py')} ③ >>> type(metadata_dict) ④ <class 'dict'> >>> list(metadata_dict.keys()) ⑤ ['romantest8.py', 'pluraltest1.py', 'pluraltest2.py', 'pluraltest5.py', 'pluraltest6.py', 'romantest7.py', 'romantest10.py', 'romantest4.py', 'romantest9.py', 'pluraltest3.py', 'romantest1.py', 'romantest2.py', 'romantest3.py', 'romantest5.py', 'romantest6.py', 'alphameticstest.py', 'pluraltest4.py'] >>> metadata_dict['alphameticstest.py'].st_size ⑥ 2509
test
的.py
文件,然后构造包含文件名和文件元信息(通过调用os.stat()
函数得到)的元组。
f
)是字典的键;冒号后面的表达式(在这个例子中是os.stat(f)
)是值。
glob.glob('*test*.py')
调用返回的文件名。
os.stat()
函数的返回值。这意味着我们可以在字典中通过文件名查找到它的文件元信息。元信息的一个部分是文件大小st_size
。这个文件alphameticstest.py
的大小是2509
字节。
同列表解析一样,你可以在字典解析中包含if
字句来过滤输入序列,对于每一个元素字句中的表达式都会被求值。
>>> import os, glob, humansize >>> metadata_dict = {f:os.stat(f) for f in glob.glob('*')} ① >>> humansize_dict = {os.path.splitext(f)[0]:humansize.approximate_size(meta.st_size) \ ... for f, meta in metadata_dict.items() if meta.st_size > 6000} ② >>> list(humansize_dict.keys()) ③ ['romantest9', 'romantest8', 'romantest7', 'romantest6', 'romantest10', 'pluraltest6'] >>> humansize_dict['romantest9'] ④ '6.5 KiB'
glob.glob('*')
),通过os.stat(f)
获得每一个文件的元信息, 然后构造一个键是文件名,值是文件元信息的字典。
6000
字节的文件(if meta.st_size > 6000
), 并用过滤出的列表构造字典, 字典的键是文件名去掉扩展名的部分(os.path.splitext(f)[0]
) ,字典的值是每个文件的人类可读的近似大小(humansize.approximate_size(meta.st_size)
)。
approximate_size()
函数返回的字符串。
这里是一个可能有用的通过字典解析实现的小技巧: 交换字典的键和值。
>>> a_dict = {'a': 1, 'b': 2, 'c': 3} >>> {value:key for key, value in a_dict.items()} {1: 'a', 2: 'b', 3: 'c'}
⁂
同样,集合也有自己的集合解析的语法。它和字典解析的非常相似,唯一的不同是集合只有值而没有键:值对。
>>> a_set = set(range(10)) >>> a_set {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} >>> {x ** 2 for x in a_set} ① {0, 1, 4, 81, 64, 9, 16, 49, 25, 36} >>> {x for x in a_set if x % 2 == 0} ② {0, 8, 2, 4, 6} >>> {2**x for x in range(10)} ③ {32, 1, 2, 4, 8, 64, 128, 256, 16, 512}
9
这个集合的的平方。
if
字句来在将元素放入结果集合前进行过滤。
⁂
os
module
os
— Portable access to operating system specific features
os.path
module
os.path
— Platform-independent manipulation of file names
glob
module
glob
— Filename pattern matching
time
module
time
— Functions for manipulating clock time
© 2001–9 Mark Pilgrim