您在這里: 主頁 深入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上。 在整本書中,你將一直看見類似下面的例子:

  1. examples 目錄導入某一個模塊
  2. 調用模塊的某一個函數
  3. 解釋輸出結果

如果你不知道當前工作目錄, 第一步很可能會得到一個ImportError。 為什么? 因為 Python 將在導入搜索路徑中查找示例模塊, 但是由于examples 目錄沒有包含在搜索路徑中,查找將失敗。 你可以通過下面兩個方法之一來解決這個問題:

  1. examples目錄加入到導入搜索路徑中
  2. 將當前工作目錄切換到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
  1. os 是Python 自帶的; 你可以在任何時間,任何地方導入它。
  2. 使用os.getcwd() 函數獲得當前工作目錄。當你運行一個圖形化的Python Shell 時,當前工作目錄默認將是Python Shell的可執行文件所在的目錄。在Windows 上, 這個目錄取決于你將Python安裝在哪里; 默認位置是 c:\Python31。如果你通過命令行運行Python Shell,當前工作目錄是你運行python3時所在的目錄。
  3. 使用os.chdir()函數改變當前工作目錄
  4. 運行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
  1. os.path.join() 函數從一個或多個路徑片段中構造一個路徑名。 在這個例子中, 它僅僅是簡單的拼接字符串.
  2. 這個例子稍微復雜一點, 在和文件名拼接前,join函數給路徑名添加一個額外的斜杠。由于我在Windows 上寫這個例子, 這個斜杠是一個反斜杠而不是正斜杠。如果你在Linux 或者Mac OS X上重現這個例子, 你將會看見正斜杠. 無論你使用哪種形式的斜杠,Python 都可以訪問到文件。
  3. os.path.expanduser() 用來將包含~符號(表示當前用戶Home目錄)的路徑擴展為完整的路徑。在任何有Home 目錄概念的操作系統上(包括Linux,Mac OS X 和Windows),這個函數都能工作。返回的路徑不以斜杠結尾,但是os.path.join()并不介意這一點。
  4. 結合這些技術,你可以很方便的構造出用戶Home 目錄下的文件和目錄的路徑。 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'
  1. split 函數分割一個完整路徑并返回目錄和文件名。
  2. 還記得我說過在函數返回多個值時應該使用多變量賦值 嗎 ? os.path.split() 函數正是這樣做的。 將split函數的返回值賦值給一個二元組。每個變量獲得了返回元組中的對應元素的值。
  3. 第一個變量dirname,獲得了os.path.split() 函數返回元組中的第一個元素,文件所在的目錄。
  4. 第二個變量filename,獲得了os.path.split() 函數返回元組中的第二個元素,文件名。
  5. 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']
  1. glob 模塊接受一個通配符并返回所有匹配的文件和目錄的路徑。在這個例子中,通配符是一個目錄名加上 “*.xml”, 它匹配examples子目錄下的所有.xml 文件。
  2. 現在我們將當前工作目錄切換到examples 目錄。 os.chdir() 可以接受相對路徑.
  3. 在glob模式中你可以使用多個通配符。這個例子在當前工作目錄中找出所有擴展名為.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)
  1. 當前工作目錄是examples 文件夾。
  2. feed.xmlexamples 文件夾中的一個文件。 調用os.stat() 函數返回一個包含多種文件元信息的對象。
  3. st_mtime 是最后修改時間,它的格式不是很有用。(技術上講,它是從紀元,也就是1970年1月1號的第一秒鐘,到現在的秒數)
  4. time 模塊是Python標準庫的一部分。 它包含用于在不同時間格式中轉換,將時間格式化成字符串以及處理時區的函數。
  5. 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'
  1. os.stat() 函數也通過st_size 屬性返回文件大小。文件feed.xml 的大小是 3070 字節。
  2. 你可以將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]
  1. 為了理解這一點,請從右向左看。 a_list是你要映射的列表。Python解釋器逐個訪問a_list的元素,并臨時將元素賦值給變量elem。 然后Python 對元素應用函數elem * 2并且將結果添加到返回列表中。
  2. 列表解析創造一個新的列表而不改變原列表。
  3. 可以安全的將列表解析的結果賦值給被映射的變量。Python會在內存中構造新的列表,在列表解析完成后將結果賦值給原來的變量。

你可以在列表解析中使用任何的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']
  1. 這里返回當前目錄下的所有.xml 文件。
  2. 列表解析接受.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']
  1. 你可以在列表解析的最后加入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')]
  1. 這個列表解析找到當前工作目錄下的所有.xml文件, 對于每一個文件構造一個包含文件大小(通過調用os.stat()獲得)和絕對路徑(通過調用os.path.realpath())的元組。
  2. 這個列表解析在前一個的基礎上對每一個.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
  1. 這不是字典解析; 而是列表解析。它找到所有名稱中包含test.py文件,然后構造包含文件名和文件元信息(通過調用os.stat()函數得到)的元組。
  2. 結果列表的每一個元素是元組。
  3. 這是一個字典解析。 除了兩點以外,它的語法同列表解析很類似。首先,它被花括號而不是方括號包圍; 第二,對于每一個元素它包含由冒號分隔的兩個表達式,而不是列表解析的一個。冒號前的表達式(在這個例子中是f)是字典的鍵;冒號后面的表達式(在這個例子中是os.stat(f))是值。
  4. 字典解析返回結果是字典。
  5. 這個字典的鍵很簡單,就是glob.glob('*test*.py')調用返回的文件名。
  6. 每一個鍵對應的值是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'
  1. 這個字典解析獲得當前目錄下所有的文件的列表(glob.glob('*')),通過os.stat(f)獲得每一個文件的元信息, 然后構造一個鍵是文件名,值是文件元信息的字典。
  2. 這個字典解析在前一個基礎上過濾掉文件小于6000字節的文件(if meta.st_size > 6000), 并用過濾出的列表構造字典, 字典的鍵是文件名去掉擴展名的部分(os.path.splitext(f)[0]) ,字典的值是每個文件的人類可讀的近似大小(humansize.approximate_size(meta.st_size))。
  3. 正如你在前一個例子中所看見的,有6個這樣的文件,所以字典中有6個元素。
  4. 每一個鍵對應的值是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}
  1. 集合解析可以接受一個集合作為參數。這個集合解析計算數字0-9這個集合的的平方。
  2. 同列表解析和字典解析一樣, 集合解析也可以包含if 字句來在將元素放入結果集合前進行過濾。
  3. 集合解析的輸入并不一定要是集合; 可以是任何序列。

進一步閱讀

© 2001–9 Mark Pilgrim

<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            亚洲欧美在线