您在這里: 主頁 ‣ 深入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