# Chapter 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](http://en.wikiquote.org/wiki/Richard_Feynman)
## 深入
這一章節將圍繞一個非常強大的技術向你介紹列表解析,字典解析和集合解析這三個概念。但是,我要先打個岔介紹兩個幫助你瀏覽本地文件系統的模塊。
## 處理文件和目錄
Python 3 帶有一個模塊叫做 `os`,代表 “操作系統(operating system)。” [`os` 模塊](http://docs.python.org/3.1/library/os.html) 包含非常多的函數用于獲取(和修改)本地目錄、文件進程、環境變量等的信息。Python 盡最大的努力在[所有支持的操作系統](installing-python.html)上提供一個統一的API, 這樣你就可以在保證程序能夠在任何的計算機上運行的同時盡量少的包含平臺特定的代碼。
### 當前工作目錄
當你剛剛開始學習Python的時候, 你將花大量的時間在 [Python Shell](installing-python.html#idle)上。 在整本書中,你將一直看見類似下面的例子:
1. 在[`examples` 目錄導入某一個模塊](examples/)
2. 調用模塊的某一個函數
3. 解釋輸出結果
總是有一個當前工作目錄
如果你不知道當前工作目錄, 第一步很可能會得到一個`ImportError`。 為什么? 因為 Python 將在[導入搜索路徑](your-first-python-program.html#importsearchpath)中查找示例模塊, 但是由于`examples` 目錄沒有包含在搜索路徑中,查找將失敗。 你可以通過下面兩個方法之一來解決這個問題:
1. 將`examples`目錄加入到導入搜索路徑中
2. 將當前工作目錄切換到`examples`目錄
Python在任何時候都在暗地里記住了當前工作目錄這個屬性。無論你是在Python Shell 中,還是在命令行運行你自己的Python 腳本,抑或是在Web 服務器上運行Python CGI 腳本,當前工作目錄總是存在。
`os` 模塊提供了兩個函數處理當前工作目錄
```
C:\Python31
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
/Users/pilgrim/diveintopython3/examples/humansize.py
/Users/pilgrim/diveintopython3/examples\humansize.py
c:\Users\pilgrim
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'
('/Users/pilgrim/diveintopython3/examples', 'humansize.py')
'/Users/pilgrim/diveintopython3/examples'
'humansize.py'
>>> shortname
'humansize'
>>> extension
'.py'
```
1. `split` 函數分割一個完整路徑并返回目錄和文件名。
2. 還記得我說過在函數返回多個值時應該使用[多變量賦值](native-datatypes.html#multivar) 嗎 ? `os.path.split()` 函數正是這樣做的。 將`split`函數的返回值賦值給一個二元組。每個變量獲得了返回元組中的對應元素的值。
3. 第一個變量`dirname`,獲得了`os.path.split()` 函數返回元組中的第一個元素,文件所在的目錄。
4. 第二個變量`filename`,獲得了`os.path.split()` 函數返回元組中的第二個元素,文件名。
5. `os.path` 也包含`os.path.splitext()` 函數,它分割一個文件名并返回短文件名和擴展名。可以使用同樣的技術將它們的值賦值給不同的變量。
### 羅列目錄內容
`glob` 模塊是Python標準庫中的另一個工具,它可以通過編程的方法獲得一個目錄的內容,并且它使用熟悉的命令行下的通配符。
`glob` 模塊使用shell風格的通配符。
```
>>> os.chdir('/Users/pilgrim/diveintopython3/')
>>> import glob
['examples\\feed-broken.xml',
'examples\\feed-ns0.xml',
'examples\\feed.xml']
['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
c:\Users\pilgrim\diveintopython3\examples
1247520344.9537716
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.xml`是`examples` 文件夾中的一個文件。 調用`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
3070
>>> import humansize
'3.0 KiB'
```
1. `os.stat()` 函數也通過`st_size` 屬性返回文件大小。文件`feed.xml` 的大小是 `3070` 字節。
2. 你可以將`st_size` 屬性作為參數傳給[`approximate_size()` 函數](your-first-python-program.html#divingin)。
### 構造絕對路徑
在[前一節中](#osstat),`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]
[2, 18, 16, 8]
[1, 9, 8, 4]
>>> a_list
[2, 18, 16, 8]
```
1. 為了理解這一點,請從右向左看。 `a_list`是你要映射的列表。Python解釋器逐個訪問`a_list`的元素,并臨時將元素賦值給變量`elem`。 然后Python 對元素應用函數``elem` * 2`并且將結果添加到返回列表中。
2. 列表解析創造一個新的列表而不改變原列表。
3. 可以安全的將列表解析的結果賦值給被映射的變量。Python會在內存中構造新的列表,在列表解析完成后將結果賦值給原來的變量。
你可以在列表解析中使用任何的Python表達式, 包括`os` 模塊中用于操作文件和目錄的函數。
```
>>> import os, glob
['feed-broken.xml', 'feed-ns0.xml', 'feed.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
['pluraltest6.py',
'romantest10.py',
'romantest6.py',
'romantest7.py',
'romantest8.py',
'romantest9.py']
```
1. 你可以在列表解析的最后加入`if`子句來過濾列表。對于列表中每一個元素`if` 關鍵字后面的表達式都會被計算。如果表達式的計算結果為`True`,那么這個元素將會被包含在輸出中。這個列表解析在當前目錄查找所有`.py` 文件,而 `if` 表達式通過測試文件大小是否大于`6000`字節對列表進行過濾。有6個符合條件的文件,所以這個列表解析返回包含六個文件名的列表。
到目前為止的例子中的列表解析都只是用了一些簡單的表達式, 乘以一個常數、調用一個函數或者是在過濾后返回原始元素。 然而列表解析并不限制表達式的復雜程度。
```
>>> import os, glob
[(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
[('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()`函數](your-first-python-program.html#divingin)。
## 字典解析
字典解析和列表解析類似,只不過它生成字典而不是列表。
```
>>> import os, glob
('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))
<class 'dict'>
['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']
2509
```
1. 這不是字典解析; 而是[列表解析](#listcomprehension)。它找到所有名稱中包含`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
['romantest9', 'romantest8', 'romantest7', 'romantest6', 'romantest10', 'pluraltest6']
'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}
{0, 1, 4, 81, 64, 9, 16, 49, 25, 36}
{0, 8, 2, 4, 6}
{32, 1, 2, 4, 8, 64, 128, 256, 16, 512}
```
1. 集合解析可以接受一個集合作為參數。這個集合解析計算數字0-`9`這個集合的的平方。
2. 同列表解析和字典解析一樣, 集合解析也可以包含`if` 字句來在將元素放入結果集合前進行過濾。
3. 集合解析的輸入并不一定要是集合; 可以是任何序列。
## 進一步閱讀
* [`os` module](http://docs.python.org/3.1/library/os.html)
* [`os`?—?Portable access to operating system specific features](http://www.doughellmann.com/PyMOTW/os/)
* [`os.path` module](http://docs.python.org/3.1/library/os.path.html)
* [`os.path`?—?Platform-independent manipulation of file names](http://www.doughellmann.com/PyMOTW/ospath/)
* [`glob` module](http://docs.python.org/3.1/library/glob.html)
* [`glob`?—?Filename pattern matching](http://www.doughellmann.com/PyMOTW/glob/)
* [`time` module](http://docs.python.org/3.1/library/time.html)
* [`time`?—?Functions for manipulating clock time](http://www.doughellmann.com/PyMOTW/time/)
* [List comprehensions](http://docs.python.org/3.1/tutorial/datastructures.html#list-comprehensions)
* [Nested list comprehensions](http://docs.python.org/3.1/tutorial/datastructures.html#nested-list-comprehensions)
* [Looping techniques](http://docs.python.org/3.1/tutorial/datastructures.html#looping-techniques)
- 版權信息
- Chapter -1 《深入 Python 3》中有何新內容
- Chapter 0 安裝 Python
- Chapter 1 你的第一個 Python 程序
- Chapter 2 內置數據類型
- Chapter 3 解析
- Chapter 4 字符串
- Chapter 5 正則表達式
- Chapter 6 閉合 與 生成器
- Chapter 7 類 & 迭代器
- Chapter 8 高級迭代器
- Chapter 9 單元測試
- Chapter 10 重構
- Chapter 11 文件
- Chapter 12 XML
- Chapter 13 序列化Python對象
- Chapter 14 HTTP Web 服務
- Chapter 15 案例研究:將chardet移植到Python 3
- Chapter 16 打包 Python 類庫
- Chapter A 使用2to3將代碼移植到Python 3
- Chapter B 特殊方法名稱
- Chapter C 接下來閱讀什么?