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上。 在整本书中,你将一直看见类似下面的例子:
总是有一个当前工作目录。如果你不知道当前工作目录, 第一步很可能会得到一个ImportError。 为什么? 因为 Python 将在导入搜索路径中查找示例模块, 但是由于examples 目录没有包含在搜索路径中,查找将失败。 你可以通过下面两个方法之一来解决这个问题:
Python 在任何时候都在暗地里记住了当前工作目录这个属性。无论你是在Python Shell 中,还是在命令行运行你自己的Python 脚本,抑或是在Web 服务器上运行Python CGI 脚本,当前工作目录总是存在。
>>> import os ①
>>> print(os.getcwd()) ②
C:\Python31
>>> os.chdir('/Users/pilgrim/diveintopython3/examples') ③
>>> print(os.getcwd()) ④
C:\Users\pilgrim\diveintopython3\examples
既然我们说到了目录,我得指出 os.path 模块。os.path模块包含了操作文件名和目录名的函数:
>>> import os
>>> print(os.path.join('/User/pligrim/diveintopython3/examples/',
'humansize.py')) ①
/User/pligrim/diveintopython3/examples/humansize.py
>>> print(os.path.join('/User/pligrim/diveintopython3/examples/',
'humansize.py')) ②
/User/pligrim/diveintopython3/examples/humansize.py
>>> print(os.path.expanduser('~')) ③
C:\Users\31592
>>> print(os.path.join(os.path.expanduser('~'), 'diveintopython3',
'exmaples', 'humasize.py')) ④
C:\Users\31592\diveintopython3\exmaples\humasize.py
os.path 也包含用于分割完整路径名,目录名和文件名的函数:
>>> pathname = '/Users/31592/diveintopython3/exmaples/humasize.py'
>>> os.path.split(pathname) ①
('/Users/31592/diveintopython3/exmaples', 'humasize.py')
>>> (dirname, filename) = os.path.split(pathname) ②
>>> dirname ③
'/Users/31592/diveintopython3/exmaples'
>>> filename ④
'humasize.py'
>>> (shortname, extension) = os.path.splitext(filename) ⑤
>>> shortname
'humasize'
>>> extension
'.py'
glob 模块是Python 标准库中的另一个工具,它可以通过编程的方法获得一个目录的内容,并且它使用熟悉的命令行下的通配符。 glob 模块使用shell 风格的通配符。
>>> 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']
每一个现代文件系统都对文件存储了元信息: 创建时间,最后修改时间,文件大小等等。Python 单独提供了一个的API 用于访问这些元信息。 你不需要打开文件。知道文件名就足够了。
>>> import os
>>> print(os.getcwd()) ①
E:\HelloWorldPython
>>> metadata = os.stat('humansize.py') ②
>>> metadata.st_mtime ③
1587036280.5743196
>>> import time ④
>>> time.localtime(metadata.st_mtime) ⑤
time.struct_time(tm_year=2020, tm_mon=4, tm_mday=16, tm_hour=19,
tm_min=24, tm_sec=40, tm_wday=3, tm_yday=107, tm_isdst=0)
# continued from the previous example
>>> metadata.st_size ①
2020
>>> import humansize
>>> humansize.approximate_size(metadata.st_size) ②
'2.0 KiB'
在前一节中,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
你可以在列表解析中使用任何的Python 表达式。
列表解析提供了一种紧凑的方式,实现了通过对列表中每一个元素应用一个函数的方法来将一个列表映射到另一个列表。
>>> 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]
你可以在列表解析中使用任何的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 ']
列表解析也可以过滤列表,生成比原列表短的结果列表。
>>> 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']
到目前为止的例子中的列表解析都只是用了一些简单的表达式, 乘以一个常数、调用一个函数或者是在过滤后返回原始元素。 然而列表解析并不限制表达式的复杂程度。
>>> 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')]
字典解析和列表解析类似,只不过它生成字典而不是列表。
>>> 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
同列表解析一样,你可以在字典解析中包含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'
这里是一个可能有用的通过字典解析实现的小技巧: 交换字典的键和值。
>>> 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, 64, 4, 36, 9, 16, 49, 81, 25}
>>> {x for x in a_set if x % 2 == 0} ②
{0, 2, 4, 6, 8}
>>> {2 ** x for x in range(10)} ③
{32, 1, 2, 64, 4, 128, 256, 512, 8, 16}