# 第十章 自帶電池
> 來源:http://www.cnblogs.com/Marlowes/p/5459376.html
> 作者:Marlowes
現在已經介紹了Python語言的大部分基礎知識。Python語言的核心非常強大,同時還提供了更多值得一試的工具。Python的標準安裝中還包括一組模塊,稱為_標準庫_(standard library)。之前已經介紹了一些模塊(例如`math`和`cmath`,其中包括了用于計算實數和復數的數學函數),但是標準庫還包含其他模塊。本章將向讀者展示這些模塊的工作方式,討論如何分析它們,學習它們所提供的功能。本章后面的內容會對標準庫進行概括,并且著重介紹一部分有用的模塊。
## 10.1 模塊
現在你已經知道如何創建和執行自己的程序(或腳本)了,也學會了怎么用`import`從外部模塊獲取函數并且為自己的程序所用:
```
>>> import math
>>> math.sin(0)
0.0
```
讓我們來看看怎樣編寫自己的模塊。
### 10.1.1 模塊是程序
任何Python程序都可以作為模塊導入。假設你寫了一個代碼清單10-1所示的程序,并且將它保存為`hello.py`文件(名字很重要)。
```
代碼清單10-1 一個簡單的模塊
# hello.py
print "Hello, world!"
```
程序保存的位置也很重要。下一節中你會了解更多這方面的知識,現在假設將它保存在`C:\python`(Windows)或者`~/python`(UNIX/Mac OS X)目錄中,接著就可以執行下面的代碼,告訴解釋器在哪里尋找模塊了(以Windows目錄為例):
```
>>> import sys
>>> sys.path.append("c:/python")
```
_注:在UNIX系統中,不能只是簡單地將字符串`"~/python"`添加到`sys.path`中,必須使用完整的路徑(例如`/home/yourusername/python`)。如果你希望將這個操作自動完成,可以使用`sys.path.expanduser("~/python")`。_
我這里所做的知識告訴解釋器:除了從默認的目錄中尋找之外,還需要從目錄`c:\python`中尋找模塊。完成這個步驟之后,就能導入自己的模塊了(存儲在`c:\python\hello.py`文件中):
```
>>> import hello
Hello, world!
```
_注:在導入模塊的時候,你可能會看到有新文件出現——在本例中是`c:\python\hello.pyc`。這個以`.pyc`為擴展名的文件是(平臺無關的)經過處理(編譯)的,已經轉換成Python能夠更加有效地處理的文件。如果稍后導入同一個模塊,Python會導入`.pyc`文件而不是`.py`文件,除非`.py`文件已改變,在這種情況下,會生成新的`.pyc`文件。刪除`.pyc`文件不會損害程序(只要等效的`.py`文件存在即可)——必要的時候系統還會創建新的`.pyc`文件。_
如你所見,在導入模塊的時候,其中的代碼被執行了。不過,如果再次導入該模塊,就什么都不會發生了:
```
>>> import hello
>>>
```
為什么這次沒用了?因為導入模塊并不意味著在導入時執行某些操作(比如打印文本)。它們主要用于_定義_,比如變量、函數和類等。此外,因為只需要定義這些東西一次,導入模塊多次和導入一次的效果是一樣的。
**為什么只是一次**
這種“只導入一次”(import-only-once)的行為在大多數情況下是一種實質性優化,對于一下情況尤其重要:兩個模塊互相導入。
在大多數情況下,你可能會編寫兩個互相訪問函數和類的模塊以便實現正確的功能。舉例來說,假設創建了兩個模塊——`clientdb`和`billing`——分別包含了用于客戶端數據庫和計費系統的代碼。客戶端數據庫可能需要調用計費系統的功能(比如每月自動將賬單發送給客戶),而計費系統可能也需要訪問客戶端數據庫的功能,以保證計費準確。
如果每個模塊都可以導入數次,那么就出問題了。模塊`clientdb`會導入`billing`,而`billing`又導入`clientdb`,然后`clientdb`又······你應該能想象到這種情況。這個時候導入就成了無限循環。(無限遞歸,記得嗎?)但是,因為在第二次導入模塊的時候什么都不會發生,所以循環會終止。
如果堅持重新載入模塊,那么可以使用內建的`reload`函數。它帶有一個參數(需要重新載入的模塊),并且返回重新載入的模塊。如果你在程序運行的時候更改了模塊并且希望將這些更改反應出來,那么這個功能會比較有用。要重新載入`hello`模塊(只包含一個`print`語句),可以像下面這樣做:
```
>>> hello = reload(hello)
Hello, world!
```
這里假設`hello`已經被導入過(一次)。那么,通過將`reload`函數的返回值賦給`hello`,我們使用重新載入的版本替換了原先的版本。如你所見,問候語已經打印出來了,在此我完成了模塊的導入。
如果你已經通過實例化`bar`模塊中的`Foo`類創建了一個對象`x`,然后重新載入bar模塊,那么不管通過什么方式都無法重新創建引用`bar`的對象`x`,`x`仍然是舊版本`Foo`類的實例(源自舊版本的`bar`)。如果需要x基于重新載入的模塊`bar`中的新`Foo`類進行創建,那么你就得重新創建它了。
注意,Python3.0已經去掉了`reload`函數。盡管使用`exec`能夠實現同樣的功能,但是應該盡可能避免重新載入模塊。
### 10.1.2 模塊用于定義
綜上所述,模塊在第一次導入到程序中時被執行。這看起來有點用——但并不算很有用。真正的用處在于它們(像類一樣)可以保持自己的作用域。這就意味著定義的所有類和函數以及賦值后的變量都成為了模塊的特性。這看起來挺復雜的,用起來卻很簡單。
1\. 在模塊中定義函數
假設我們編寫了一個類似代碼清單10-2的模塊,并且將它存儲為hello2.py文件。同時,假設我們將它放置到Python解釋器能夠找到的地方——可以使用前一節中的`sys.path`方法,也可以用10.1.3節中的常規方法。
注:如果希望模塊能夠像程序一樣被執行(這里的程序是用于執行的,而不是真正作為模塊使用的),可以對Python解釋器使用`-m`切換開關來執行程序。如果`progname.py`(注意后綴)文件和其他模塊都已被安裝(也就是導入了`progname`),那么運行python `-m progname args`命令就會運行帶命令行參數`args`的`progname`程序。
```
代碼清單10-2 包含函數的簡單模塊
# hello2.py
def hello():
print "Hello, world!"
```
可以像下面這樣導入:
```
>>> import hello2
```
模塊就會被執行,這意味著`hello`函數在模塊的作用域被定義了。因此可以通過以下方式來訪問函數:
```
>>> hello2.hello()
Hello, world!
```
我們可以通過同樣的方法來使用如何在模塊的全局作用域中定義的名稱。
我們為什么要這樣做呢?為什么不在主程序中定義好一切呢?主要原因是_代碼重用_(code reuse)。如果把代碼放在模塊中,就可以在多個程序中使用這些代碼了。這意味著如果編寫了一個非常棒的客戶端數據庫,并且將它放在叫做`clientdb`的模塊中,那么你就可以在計費的時候、發送垃圾郵件的時候(當然我可不希望你這么做)以及任何需要訪問客戶數據的程序中使用這個模塊了。如果沒有將這段代碼放在單獨的模塊中,那么就需要在每個程序中重寫這些代碼了。因此請記住:為了讓代碼可重用,請將它模塊化!(是的,這當然也關乎抽象)
2\. 在模塊中增加測試代碼
模塊被用來定義函數、類和其他一些內容,但是有些時候(事實上是經常),在模塊中添加一些檢查模塊本身是否能正常工作的測試代碼是很有用的。舉例來說,假如想要確保`hello`函數正常工作,你可能會將`hello2`模塊重寫為新的模塊——代碼清單10-3中定義的`hello3`。
```
# hello3.py
def hello():
print "Hello, world!"
# A test
hello()
```
這看起來是合理的,如果將它作為普通程序運行,會發現它能夠正常工作。但如果將它作為模塊導入,然后在其他程序中使用hello函數,測試代碼就會被執行,就像本章實驗開頭的第一個`hello`模塊一樣:
```
>>> import hello3
Hello, world!
>>> hello3.hello.()
Hello, world!
```
這個可不是你想要的。避免這種情況關鍵在于:“告知”模塊本身是作為程序運行還是導入到其他程序。為了實現這一點,需要使用`__name__`變量:
```
>>> __name__
'__main__'
>>> hello3.__name__
'hello3'
```
如你所見,在“主程序”(包括解釋器的交互式提示符在內)中,變量`__name__`的值是`'__main__'`。而在導入的模塊中,這個值就被設定為模塊的名字。因此,為了讓模塊的測試代碼更加好用,可以將其放置在if語句中,如代碼清單10-4所示。
```
代碼清單10-4 使用條件測試代碼的模塊 # hello4.py
def hello():
print "Hello, world!"
def test():
hello()
if __name__ = '__main__':
test()
```
如果將它作為程序運行,`hello`函數會被執行。而作為模塊導入時,它的行為就會像普通模塊一樣:
```
>>> import hello4
>>> hello4.hello()
Hello, world!
```
如你所見,我將測試代碼放在了`test`函數中,也可以直接將它們放入`if`語句。但是,將測試代碼放入獨立的`test`函數會更靈活,這樣做即使在把模塊導入其他程序之后,仍然可以對其進行測試:
```
>>> hello4.test()
Hello, world!
```
_注:如果需要編寫更完整的測試代碼,將其放置在單獨的程序中會更好。關于編寫測試代碼的更多內容,參見第16章。_
### 10.1.3 讓你的模塊可用
前面的例子中,我改變了`sys.path`,其中包含了(字符串組成的)一個目錄列表,解釋器在該列表中查找模塊。然而一般來說,你可能不想這么做。在理想情況下,一開始`sys.path`本身就應該包含正確的目錄(包括模塊的目錄)。有兩種方法可以做到這一點:一是將模塊放置在合適的位置,另外則是告訴解釋器去哪里查找需要的模塊。下面幾節將探討這兩種方法。
1\. 將模塊放置在正確位置
將模塊放置在正確位置(或者說某個正確位置,因為會有多種可能性)是很容易的。只需要找出Python解釋器從哪里查找模塊,然后將自己的文件放置在那里即可。
_注:如果機器上面的Python解釋器是由管理員安裝的,而你又沒有管理員權限,可能無法將模塊存儲在Python使用的目錄中。這種情況下,你需要使用另外一個解決方案:告訴解釋器去那里查找。_
你可能記得,那些(成為搜索路徑的)目錄的列表可以在`sys`模塊中的`path`變量中找到:
```
>>> import sys, pprint
>>> pprint.pprint(sys.path)
['', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-x86_64-linux-gnu', '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload', '/usr/local/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages/PILcompat', '/usr/lib/python2.7/dist-packages/gtk-2.0', '/usr/lib/python2.7/dist-packages/ubuntu-sso-client']
```
_注:如果你的數據結構過大,不能在一行打印完,可以使用`pprint`模塊中的`pprint`函數替代普通的`print`語句。`pprint`是個相當好的打印函數,能夠提供更加智能的打印輸出。_
這是安裝在elementary OS上的Python2.7的標準路徑,不同的系統會有不同的結果。關鍵在于每個字符串都提供了一個放置模塊的目錄,解釋器可以從這些目錄中找到所需的模塊。盡管這些目錄都可以使用,但`site-packages`目錄是最佳的選擇,因為它就是用來做這些事情的。查看你自己的`sys.path`,找到`site-packages`目錄,將代碼清單10-4的模塊存儲在其中,要記得改名,比如改成`another_hello.py`,然后測試:
```
>>> import another_hello
>>> another_hello.hello()
Hello, world!
```
只要將模塊放入類似`site-packages`這樣的目錄中,所有程序就都能將其導入了。
2\. 告訴編譯器去那里找
“將模塊放置在正確的位置”這個解決方案對于以下幾種情況可能并不適用:
? 不希望將自己的模塊填滿Python解釋器的目錄;
? 沒有在Python解釋器目錄中存儲文件的權限;
? 想將模塊放在其他地方。
最后一點是“想將模塊放在其他地方”,那么就要告訴解釋器去哪里找。你之前已經看到了一種方法,就是編輯`sys.path`,但這不是通用的方法。標準的實現方法是在`PYTHONPATH`環境變量中包含模塊所在的目錄。
`PYTHONPATH`環境變量的內容會因為使用的操作系統不同而有所差異(參見下面的“環境變量”),但基本上來說,它與`sys.path`很類似——一個目錄列表。
**環境變量**
環境變量并不是Python解釋器的一部分——它們是操作系統的一部分。基本上,它相當于Python變量,不過是在Python解釋器外設置的。有關設置的方法,你應該參考操作系統文檔,這里只給出一些相關提示。
在UNIX和Mac OS X中,你可以在一些每次登陸都要執行的shell文件內設置環境變量。如果你使用類似bash的shell文件,那么要設置的就是`.bashrc`,你可以在主目錄中找到它。將下面的命令添加到這個文件中,從而將`~/python`加入到`PYTHONPATH`:
```
export PYTHON=$PYTHONPATH:~/python
```
注意,多個路徑以冒號分隔。其他的shell可能會有不同的語法,所以你應該參考相關的文檔。
對于Windows系統,你可以使用控制面板編輯變量(適用于高級版本的Windows,比如Windows XP、2000、NT和Vista,舊版本的,比如Windows 98就不適用了,而需要修改`autoexec.bat`文件,下段會講到)。依次點擊開始菜單→設置→控制面板。進入控制面板后,雙擊“系統”圖標。在打開的對話框中選擇“高級”選項卡,點擊“環境變量”按鈕。這時會彈出一個分為上下兩欄的對話框:其中一欄是用戶變量,另外一欄就是系統變量,需要修改的是用戶變量。如果你看到其中已經有`PYTHONPATH`項,那么選中它,單擊“編輯”按鈕進行編輯。如果沒有,單擊“新建”按鈕,然后使用`PYTHONPATH`作為“變量名”,輸入目錄作為“變量值”。注意,多個目錄以分號分分隔。
如果上面的方法不行,你可以編輯`autoexec.bat`文件,該文件可以在C盤的根目錄下找到(假設是以標準模式安裝的Windows)。用記事本(或者IDLE編輯器)打開它,增加一行設置`PYTHONPATH`的內容。如果想要增加目錄`C:\pyrhon`。可以像下面這樣做:
```
set PYTHONPATH=%PYTHONPATH%;C:\python
```
注意,你所使用的IDE可能會有自身的機制,用于設置環境變量和Python路徑。
_注:你不需要使用`PYTHONPATH`來更改`sys.path`。路徑配置文件提供了一個有用的捷徑,可以讓Python替你完成這些工作。路徑配置文件是以`.pth`為擴展名的文件,包括應該添加到`sys.path`中的目錄信息。空行和以`#`開頭的行都會被忽略。以`import`開頭的文件會被執行。為了執行路徑配置文件,需要將其放置在可以找到的地方。對于Windows來說,使用`sys.prefix`定義的目錄名(可能類似于`C:\Python22`);在UNIX和Mac OS X中則使用`site-packages`目錄(更多信息可以參見Python庫參考中`site`模塊的內容,這個模板在Python解釋器初始化時會自動導入)。_
3.命名模塊
你可能注意到了,包含模塊代碼的文件的名字要和模塊名一樣,再加上`.py`擴展名。在Windows系統中,你也可以使用`.pyw`擴展名。有關文件擴展名含義的更多信息請參見第12章。
### 10.1.4 包
為了組織好模塊,你可以將它們分組為_包_(package)。包基本上就是另外一個類模塊,有趣的地方就是它們能包含其他模塊。當模塊存儲在文件中時(擴展名`.py`),包就是模塊所在的目錄。為了讓Python將其作為包對待,它必須包含一個命名為`__init__.py`的文件(模塊)。如果將它作為普通模塊導入的話,文件的內容就是包的內容。比如有個名為`constants`的包,文件`constants/__init__.py`包括語句`PI=3.14`,那么你可以像下面這么做:
```
import constants
print constants.PI
```
為了將模塊放置在包內,直接把模塊放在包目錄內即可。
比如,如果要建立一個叫做`drawing`的包,其中包括名為`shapes`和`colors`的模塊,你就需要創建表10-1中所示的文件和目錄(UNIX路徑名)。
表10-1 簡單的包布局
```
~/python/ PYTHONPATH中的目錄
~/python/drawing/ 包目錄(drawing包)
~/python/drawing/__init__.py 包代碼(drawing模塊)
~/python/drawing/colors.py colors模塊
~/python/drawing/shapes.py shapes模塊
```
對于表10-1中的內容,假定你已經將目錄`~/python`放置在`PYTHONPATH`。在Windows系統中,只要用`C:\python`替換`~/python`,并且將正斜線為反斜線即可。
依照這個設置,下面的語句都是合法的:
```
import drawing # (1) Imports the drawing package
import drawing.colors # (2) Imports the colors module
from drawing import shapes # (3) Imports the shapes module
```
在第1條語句`drawing`中`__init__`模塊的內容是可用的,但`drawing`和`colors`模塊則不可用。在執行第2條語句之后,`colors`模塊可用了,可以通過短名(也就是僅使用`shapes`)來使用。注意,這些語句只是例子,執行順序并不是必需的。例如,不用像我一樣,在導入包的模塊前導入包本身,第2條語句可以獨立使用,第3條語句也一樣。我們還可以在包之間進行嵌套。
## 10.2 探究模塊
在講述標準庫模塊前,先教你如何獨立地探究模塊。這種技能極有價值,因為作為Python程序員,在職業生涯中可能會遇到很多有用的模塊,我又不能在這里一一介紹。目前的標準庫已經大到可以出本書了(事實上已經有這類書了),而且它還在增長。每次新的模塊發布后,都會添加到標準庫,一些模塊經常發生一些細微的變化和改進。同時,你還能在網上找到些有用的模塊并且可以很快理解(grok)它們,從而讓編程輕而易舉地稱為一種享受。
### 10.2.1 模塊中有什么
探究模塊最直接的方式就是在Python解釋器中研究它們。當然,要做的第一件事就是導入它。假設你聽說有個叫做`copy`的標準模塊:
```
>>> import copy
```
沒有引發異常,所以它是存在的。但是它能做什么?它又有什么?
1\. 使用dir
查看模塊包含的內容可以使用`dir`函數,它會將對象的所有特性(以及模塊的所有函數、類、變量等)列出。如果想要打印出`dir(copy)`的內容,你會看到一長串的名字(試試看)。一些名字以下劃線開始,暗示(約定俗成)它們并不是為在模塊外部使用而準備的。所以讓我們用列表推導式(如果不記得如何使用了,請參見5.6節)過濾掉它們:
```
>>> [n for n in dir(copy) if not n.startswith("_")]
['Error', 'PyStringMap', 'copy', 'deepcopy', 'dispatch_table', 'error', 'name', 't', 'weakref']
```
這個列表推導式是個包含`dir(copy)`中所有不以下劃線開頭的名字的列表。它比完整列表要清楚些。(如果喜歡用`tab`實現,那么應該看看庫參考中的`readline`和`rlcompleter`模塊。它們在探究模塊時很有用)
2\. `__all__`變量
在上一節中,通過列表推導式所做的事情是推測我可能會在`copy`模塊章看到什么。但是我們可以直接從列表本身獲得正確答案。在完整的`dir(copy)`列表中,你可能注意到了`__all__`這個名字。這個變量包含一個列表,該列表與我之前通過列表推導式創建的列表很類似——除了這個列表在模塊本身中已被默認設置。我們來看看它都包含哪些內容:
```
>>> copy.__all__
['Error', 'copy', 'deepcopy']
```
我的猜測還不算太離譜吧。列表推導式得到的列表只是多出了幾個我用不到的名字。但是`__all__`列表從哪來,它為什么會在那兒?第一個問題很容易回答。它是在`copy`模塊內部被設置的,像下面這樣(從`copy.py`直接復制而來的代碼):
```
__all__ =
["Error", "copy", "deepcopy"]
```
那么它為什么在那呢?它定義了模塊的公有接口(public interface)。更準確地說,它告訴解釋器:從模塊導入所有名字代表什么含義。因此,如果你使用如下代碼:
```
from copy import *
```
那么,你就能使用`__all__`變量中的4個函數。要導入`PyStringMap`的話,你就得顯示地實現,或者導入`copy`然后使用`copy.PyStringMap`,或者使用`from copy import PyStringMap`。
在編寫模塊的時候,像設置`__all__`這樣的技術是相當有用的。因為模塊中可能會有一大堆其他程序不需要或不想要的變量、函數和類,`__all__`會“客氣地”將它們過濾了出去。如果沒有設定`__all__`,用`import *`語句默認將會導入模塊中所有不以下劃線開頭的全局名稱。
### 10.2.2 用`help`獲取幫助
目前為止,你已經通過自己的創造力和Python的多個函數和特殊特性的知識探究了`copy`模塊。對于這樣的探究工作,交互式解釋器是個非常強大的工具,而對該語言的精通程度決定了對模塊探究的深度。不過,還有個標準函數能夠為你提供日常所需的信息,這個函數叫做`help`。讓我們先用`copy`函數試試:
```
>>> help(copy.copy)
Help on function copy in module copy:
copy(x)
Shallow copy operation on arbitrary Python objects.
See the module's __doc__ string for more info.
```
這些內容告訴你:`copy`帶有一個參數x,并且是“淺復制操作”。但是它還提到了模塊的`__doc__`字符串。這是什么呢?你可能記得第六章提到的文檔字符串,它就是寫在函數開頭并且簡述函數功能的字符串,這個字符串可以通過函數的`__doc__`特性引用。就像從上面的幫助文本中所理解到的一樣,模塊也可以有文檔字符串(寫在模塊開頭),類也一樣(寫在類開頭)。
事實上,前面的幫助文本是從`copy`函數的文檔字符串中取出的。
```
>>> print copy.copy.__doc__
Shallow copy operation on arbitrary Python objects.
See the module's __doc__ string for more info.
```
使用`help`與直接檢查文檔字符串相比,它的好處在于會獲得更多信息,比如函數簽名(也就是所帶的參數)。試著調用`help(copy)`(對模塊本身)看看得到什么。它會打印出很多信息,包括`copy`和`deepcopy`之間區別的透徹的討論(從本質來說,`deepcopy(x)`會將存儲在`x`中的值作為屬性進行復制,而`copy(x)`只是復制x,將x中的值綁定到副本的屬性上)。
### 10.2.3 文檔
模塊信息的自然來源當然是文件。我把對文檔的討論推后在這里,是因為自己先檢查模塊總是快一些。舉例來說,你可能會問“`range`的參數是什么”。不用在Python數據或者標準Python文檔中尋找有關`range`的描述,而是可以直接查看:
```
>>> print range.__doc__
range(stop) -> list of integers
range(start, stop[, step]) -> list of integers
Return a list containing an arithmetic progression of integers.
range(i, j) returns [i, i+1, i+2, ..., j-1]; start (!) defaults to 0.
When step is given, it specifies the increment (or decrement).
For example, range(4) returns [0, 1, 2, 3]. The end point is omitted!
These are exactly the valid indices for a list of 4 elements.
```
這樣就獲得了關于`range`函數的精確描述,因為Python解釋器可能已經運行了(在編程的時候,經常會像這樣懷疑函數的功能),訪問這些信息花不了幾秒鐘。
但是,并非每個模塊和函數都有不錯的文檔字符串(盡管都應該有),有些時候可能需要十分透徹地描述這些模塊和函數是如何工作的。大多數從網上下載的模塊都有相關的文檔。在我看來,學習Python編程最有用的文檔莫過于Python庫參考,它對所有標準庫中的模塊都有描述。如果想要查看Python的知識。十有八九我都會去查閱它。[庫參考](http://python.org/doc/lib)可以在線瀏覽,并且提供下載,其他一些標準文檔(比如Python指南或者Python語言參考)也是如此。所有這些文檔都可以在[Python網站上](http://python.org/doc)找到。
### 10.2.4 使用源代碼
到目前為止,所討論的探究技術在大多數情況下都已經夠用了。但是,對于希望真正理解Python語言的人來說,要了解模塊,是不能脫離源代碼的。閱讀源代碼,事實上是學習Python最好的方式,除了自己編寫代碼外。
真正的閱讀不是問題,但是問題在于源代碼在哪里。假設我們希望閱讀標準模塊`copy`的源代碼,去哪里找呢?一種方案是檢查`sys.pat`h,然后自己找,就像解釋器做的一樣。另外一種快捷的方法是檢查模塊的`__file__`屬性:
```
>>> print copy.__file__
C:\Python27\lib\copy.pyc
```
_注:如果文件名以`.pyc`結尾,只要查看對應的以`.py`結尾的文件即可。_
就在那!你可以使用代碼編輯器打開`copy.py`(比如IDLE),然后查看它是如何工作的。
_注:在文本編輯器中打開標準庫文件的時候,你也承擔著意外修改它的風險。這樣做可能會破壞它,所以在關閉文件的時候,你必須確保沒有保存任何可能做出的修改。_
注意,一些模塊并不包含任何可以閱讀的Python源代碼。它們可能已經融入到解釋器內了(比如`sys`模塊),或者可能是使用C程序語言寫成的(如果模塊是使用C語言編寫的,你也可以查看它的C源代碼)。(請查看第17章以獲得更多使用C語言擴展Python的信息)
## 10.3 標準庫:一些最愛
有的讀者會覺得本章的標題不知所云。“充電時刻”(batteries included)這個短語最開始由Frank Stajano創造,用于描述Python豐富的標準庫。安裝Python后,你就“免費”獲得了很多有用的模塊(充電電池)。因為獲得這些模塊的更多信息的方式很多(在本章的第一部分已經解釋過了),我不會在這里列出完整的參考資料(因為要占去很大篇幅),但是我會對一些我最喜歡的標準模塊進行說明,從而激發你對模塊進行探究的興趣。你會在“項目章節”(第20章~第29章)碰到更多的標準模塊。模塊的描述并不完全,但是會強調每個模塊比較有趣的特征。
### 10.3.1 `sys`
`sys`這個模塊讓你能夠訪問與Python解釋器聯系緊密的變量和函數,其中一些在表10-2中列出。
表10-2 `sys`模塊中一些重要的函數和變量
```
argv 命令行參數,包括腳本名稱
exit([arg]) 退出當前的程序,可選參數為給定的返回值或者錯誤信息
modules 映射模塊名字到載入模塊的字典
path 查找模塊所在目錄的目錄名列表
platform 類似sunos5或者win32的平臺標識符
stdin 標準輸入流——一個類文件(file-like)對象
stdout 標準輸出流——一個類文件對象
stderr 標準錯誤流——一個類文件對象
```
變量`sys.argv`包含傳遞到Python解釋器的參數,包括腳本名稱。
函數`sys.exit`可以退出當前程序(如果在`try/finally`塊中調用,`finally`子句的內容仍然會被執行,第八章對此進行了探討)。你可以提供一個整數作為參數,用來標識程序是否成功運行,這是UNIX的一個慣例。大多數情況下使用該整數的默認值就可以了(也就是0,表示成功)。或者你也可以提供字符串參數,用作錯誤信息,這對于用戶找出程序停止運行的原因會很有用。這樣,程序就會在退出的時候提供錯誤信息和標識程序運行失敗的代碼。
映射`sys.modules`將模塊名映射到實際存在的模塊上,它只應用于目前導入的模塊。
`sys.path`模塊變量在本章前面討論過,它是一個字符串列表,其中的每個字符串都是一個目錄名,在`import`語句執行時,解釋器就會從這些目錄中查找模塊。
`sys.platform`模塊變量(它是個字符串)是解釋器正在其上運行的“平臺”名稱。它可能是標識操作系統的名字(比如`sunos5`或`win32`),也可能標識其他種類的平臺,如果運行Jython的話,就是Java的虛擬機(比如`java1.4.0`)。
`sys.stdin`、`sys.stdout`和`sys.stderr`模塊變量是類文件流對象。它們表示標準UNIX概念中的標準輸入、標準輸出和標準錯誤。簡單來說,Python利用`sys.stdin`獲得輸入(比如用于函數`input`和`raw_input`中的輸入),利用`sys.stdout`輸出。第十一章會介紹更多有關于文件(以及這三個流)的知識。
舉例來說,我們思考一下反序打印參數的問題。當你通過命令行調用Python腳本時,可能會在后面加上一些參數——這就是_命令行參數_(command-line argument)。這些參數會放置在`sys.argv`列表中,腳本的名字為`sys.argv[0]`。反序打印這些參數很簡單,如代碼清單10-5所示。
```
# 代碼清單10-5 反序打印命令行參數
import sys
args = sys.argv[1:]
args.reverse()
print " ".join(args)
```
正如你看到的,我對`sys.argv`進行了復制。你可以修改原始的列表,但是這樣做通常是不安全的,因為程序的其他部分可能也需要包含原始參數的`sys.argv`。注意,我跳過了`sys.argv`的第一個元素,這是腳本的名字。我使用`args.reverse()`方法對列表進行反向排序,但是不能打印出這個操作結果的,這是個返回`None`的原地修改操作。下面是另外一種做法:
```
print " ".join(reversed(sys.argv[1:]))
```
最后,為了保證輸出得更好,我使用了字符串方法`join`。讓我們試試看結果如何(我使用的是MS-DOS,在UNIX Shell下它也會工作的同樣好):
```
D:\Workspace\Basic tutorial>python Code10-5.py
this is a test
test a is this
```
### 10.3.2 `os`
`os`模塊提供了訪問多個操作系統服務的功能。`os`模塊包括的內容很多,表10-3中只是其中一些最有用的函數和變量。另外,`os`和它的子模塊`os.path`還包括一些用于檢查、構造、刪除目錄和文件的函數,以及一些處理路徑的函數(例如,`os.path.split`和`os.path.join`讓你在大部分情況下都可以忽略`os.pathsep`)。關于它的更多信息,請參見標準庫文檔。
表10-3 `os`模塊中一些重要函數和變量
```
environ 對環境變量進行映射
system(command) 在子shell中執行操作系統命令
sep 路徑中的分隔符
pathsep ? 分隔路徑的分隔符
linesep 行分隔符("\n", "\r", or "\r\n")
urandom(n) 返回n字節的加密強隨機數據
```
`os.environ`映射包含本章前面講述過的環境變量。比如要訪問系統變量`PYTHONPATH`,可以使用表達式`os.environ["PYTHONPATH"]`。這個映射也可以用來更改系統環境變量,不過并非所有系統都支持。
`os.system`函數用于運行外部程序。也有一些函數可以執行外部程序。還有`open`,它可以創建與程序連接的類文件。
關于這些函數的更多信息,請參見標準庫文檔。
_注:當前版本的Python中,包括`subprocess`模塊,它包括了`os.system`、`execv`和`open`函數的功能。_
`os.sep`模塊變量是用于路徑名字中的分隔符。UNIX(以及Mac OS X中命令行版本的Python)中的標準分隔符是`"/"`,Windows中的是`"\\"`(即Python針對單個反斜線的語法),而Mac OS中的是`":"`(有些平臺上,`os.altsep`包含可選的路徑分隔符,比如Windows中的`"/"`)。
你可以在組織路徑的時候使用`os.pathsep`,就像在`PYTHONPATH`中一樣。`pathsep`用于分割路徑名:UNIX(以及Mac OS X中的命令行版本的Python)使用`":"`,Windows使用`";"`,Mac OS使用`"::"`。
模塊變量`os.linesep`用于文本文件的字符串分隔符。UNIX中(以及Mac OS X中命令行版本的Python)為一個換行符(`\n`),Mac OS中為單個回車符(`\r`),而在Windows中則是兩者的組合(`\r\n`)。
`urandom`函數使用一個依賴于系統的"真"(至少是足夠強度加密的)隨機數的源。如果正在使用的平臺不支持它,你會得到`NotImplementedError`異常。
例如,有關啟動網絡瀏覽器的問題。`system`這個命令可以用來執行外部程序,這在可以通過命令行執行程序(或命令)的環境中很有用。例如在UNIX系統中,你可以用它來列出某個目錄的內容以及發送Email,等等。同時,它對在圖形用戶界面中啟動程序也很有用,比如網絡瀏覽器。在UNIX中,你可以使用下面的代碼(假設`/usr/bin/firefox`路徑下有一個瀏覽器):
```
os.system("/usr/bin/firefox")
```
以下是Windows版本的調用代碼(也同樣假設使用瀏覽器的安裝路徑):
```
os.system(r"C:\'Program Files'\'Mozilla Firefox'\firefox.exe")
```
注意,我很仔細地將`Program Files`和`Mozilla Firefox`放入引號中,不然DOS(它負責處理這個命令)就會在空格處停不下來(對于在`PYTHONPATH`中設定的目錄來說,這點也同樣重要)。同時,注意必須使用反斜線,因為DOS會被正斜線弄糊涂。如果運行程序,你會注意到瀏覽器會試圖打開叫做`Files'\Mozilla...`的網站——也就是在空格后面的命令部分。另一方面,如果試圖在IDLE中運行該代碼,你會看到DOS窗口出現了,但是沒有啟動瀏覽器并沒有出現,除非關閉DOS窗口。總之,使用以上代碼并不是完美的解決方法。
另外一個可以更好地解決問題的函數是Windows特有的函數——`os.startfile`:
```
os.startfile(r"C:\Program Files\Mozilla Firefox\firefox.exe")
```
可以看到,`os.startfile`接受一般路徑,就算包含空格也沒問題(也就是不用像在`os.system`例子中那樣將`Program Files`放在引號中)。
注意,在Windows中,由`os.system`(或者`os.startfile`)啟動了外部程序之后,Python程序仍然會繼續運行,而在UNIX中,程序則會中止,等待`os.system`命令完成。
**更好的解決方案:`WEBBROWSER`**
在大多數情況下,`os.system`函數很有用,但是對于啟動瀏覽器這樣特定的任務來說,還有更好的解決方案:`webbrowser`模塊。它包括`open`函數,它可以自動啟動Web瀏覽器訪問給定的URL。例如,如果希望程序使用Web瀏覽器打開Python的網站(啟動新瀏覽器或者使用已經運行的瀏覽器),那么可以使用以下代碼:
```
import webbrowser
webbrowser.open("http://www.python.org")
```
### 10.3.3 `fileinput`
第十一章將會介紹很多讀寫文件的知識,現在先做個簡短的介紹。`fileinput`模塊讓你能夠輕松地遍歷文本文件的所有行。如果通過以下方式調用腳本(假設在UNIX命令行下):
```
$ python some_script.py file1.txt file2.txt file3.txt
```
這樣就可以以此對`file1.txt`到`file3.txt`文件中的所有行進行遍歷了。你還能對提供給標準輸入(`sys.stdin`,記得嗎)的文本進行遍歷。比如在UNIX的管道中,使用標準的UNIX命令`cat`:
```
$ cat file.txt | python some_script.py
```
如果使用`fileinput`模塊,在UNIX管道中使用`cat`來調用腳本的效果和將文件名作為命令行參數提供給腳本是一樣的。`fileinput`模塊最重要的函數如表10-4所示。
`fileinput.input`是其中最重要的函數。它會返回能夠于`for`循環遍歷的對象。如果不想使用默認行為(`fileinput`查找需要循環遍歷的文件),那么可以給函數提供(序列形式的)一個或多個文件名。你還能將`inplace`參數設為其真值(inplace=True)以進行原地處理。對于要訪問的每一行,需要打印出替代的內容,以返回到當前的輸入文件中。在進行原地處理的時候,可選的`backup`參數將文件名擴展備份到通過原始文件創建的備份文件中。
表10-4 fileinput模塊中重要的函數
```
input(files[, inplace[, backup]]) 便于遍歷多個輸入流中的行
filename() 返回當前文件的名稱
lineno() 返回當前(累計)的行數
filelineno() 返回當前文件的行數
isfirstline() 檢查當前行是否是文件的第一行
isstdin() 檢查最后一行是否來自sys.stdin
nextfile() 關閉當前文件,移動到下一個文件
close() 關閉序列
```
`fileinput.filename`函數返回當前正在處理的文件名(也就是包含了當前正在處理的文本行的文件)。
`fileinput.lineno`返回當前行的行數。這個數值是累計的,所以在完成一個文件的處理并且開始處理下一個文件的時候,行數并不會重置。而是將上一個文件的最后行數加1作為計數的起始。
`fileinput.filelineno`函數返回當前處理文件的當前行數。每次處理完一個文件并且開始處理下一個文件時,行數都會重置為1,然后重新開始計數。
`fileinput.isfirstline`函數在當前行是當前文件的第一行時返回真值,反之返回假值。
`fileinput.isstdin`函數在當前文件為`sys.stdin`時返回真值,否則返回假值。
`fileinput.nextfile`函數會關閉當前文件,跳到下一個文件,跳過的行并不計。在你知道當前文件已經處理完的情況下,這個函數就比較有用了——比如每個文件都包含經過排序的單詞,而你需要查找某個詞。如果已經在排序中找到了這個詞的位置,那么你就能放心地跳到下一個文件了。
`fileinput.close`函數關閉整個文件鏈,結束迭代。
為了演示`fileinput`的使用,我們假設已經編寫了一個Python腳本,現在想要為其代碼進行編號。為了讓程序在完成代碼行編號之后仍然能夠正常運行,我們必須通過在每一行的右側加上作為注釋的行號來完成編號工作。我們可以使用字符串格式化來將代碼行和注釋排成一行。假設每個程序行最多有45個字符,然后把行號注釋加在后面。代碼清單10-6展示了使用`fileinput`以及`inplace`參數來完成這項工作的簡單方法:
```
# 代碼清單10-6 為Python腳本添加行號
#!/usr/bin/env python
# coding=utf-8
# numberlines.py
import fileinput for line in fileinput.input(inplace=True):
line = line.rstrip()
num = fileinput.lineno()
print "%-40s # %2i" % (line, num)
```
如果你像下面這樣在程序本身上運行這個程序:
```
$ python numberline.py numberline.py
```
程序會變成類似于代碼清單10-7那樣。注意,程序本身已經被更改了,如果這樣運行多次,最終會在每一行中添加多個行號。我們可以回憶一下之前的內容:`rstrip`是可以返回字符串副本的字符串方法,右側的空格都被刪除(請參見3.4節,以及附錄B中的表B-6)。
```
# 代碼清單10-7 為已編號的行進行編號
#!/usr/bin/env python # 1
# coding=utf-8 # 2
# 3
# numberline.py # 4
# 5
import fileinput # 6
# 7
for line in fileinput.input(inplace=True): # 8
line = line.rstrip() # 9
num = fileinput.lineno() # 10
# 11
print "%-45s # %2i" % (line, num) # 12
```
_注:要小心使用`inplace`參數,它很容易破壞文件。你應該在不使用`inplace`設置的情況下仔細測試自己的程序(這樣只會打印出錯誤),在確保程序工作正常后再修改文件。_
另外一個使用`fileinput`的例子,請參見本章后面的`random`模塊部分。
### 10.3.4 集合、堆和雙端隊列
在程序設計中,我們會遇到很多有用的數據結構,而Python支持其中一些相對通用的類型,例如字典(或者說散列表)、列表(或者說動態數組)是語言必不可少的一部分。其他一些數據結構盡管不是那么重要,但有些時候也能派上用場。
1\. 集合
集合(set)在Python2.3才引入。`Set`類位于`sets`模塊中。盡管可以在現在的代碼中創建`Set`實例。但是除非想要兼容以前的程序,否則沒有什么必要這樣做。在Python2.3中,集合通過`set`類型的實例成為了語言的一部分,這意味著不需要導入`sets`模塊——直接創建集合即可:
```
>>> set(range(10))
set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
```
集合是由序列(或者其他可迭代的對象)構建的。它們主要用于檢查成員資格,因此副本是被忽略的:
```
>>> set(["fee", "fie", "foe"])
set(['foe', 'fee', 'fie'])
```
除了檢查成員資格外,還可以使用標準的集合操作(可能你是通過數學了解到的),比如求并集和交集,可以使用方法,也可以使用對整數進行位操作時使用的操作(參見附錄B)。比如想要找出兩個集合的并集,可以使用其中一個集合的`union`方法或者使用按位與(OR)運算符`"|"`:
```
>>> a = set([1, 2, 3])
>>> b = set([2, 3, 4])
>>> a.union(b)
set([1, 2, 3, 4])
>>> a | b
set([1, 2, 3, 4])
```
以下列出了一些其他方法和對應的運算符,方法的名稱已經清楚地表明了其用途:
```
>>> c = a & b
>>> c.issubset(a)
True
>>> c <= a
True
>>> c.issuperset(a)
False
>>> c >= a
False
>>> a.intersection(b)
set([2, 3])
>>> a & b
set([2, 3])
>>> a.difference(b)
set([1])
>>> a - b
set([1])
>>> a.symmetric_difference(b)
set([1, 4])
>>> a ^ b
set([1, 4])
>>> a.copy()
set([1, 2, 3])
>>> a.copy() is a
False
```
還有一些原地運算符和對應的方法,以及基本方法`add`和`remove`。關于這方面更多的信息,請參看[Python庫參考的3.7節](http://python.org/doc/lib/types-set.html)。
_注:如果需要一個函數,用于查找并且打印兩個集合的并集,可以使用來自`set`類型的`union`方法的未綁定版本。這種做法很有用,比如結合`reduce`來使用:_
```
>>> mySets = [] >>> for i in range(10):
... mySets.append(set(range(i, i + 5)))
...
>>> reduce(set.union, mySets)
set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13])
```
集合是可變的,所以不能用做字典中的鍵。另外一個問題就是集合本身只能包含不可變(可散列的)值,所以也就不能包含其他集合。在實際當中,集合的集合是很常用的,所以這個就是個問題了。幸好還有個`frozenset`類型,用于代表_不可變_(可散列)的集合:
```
>>> a = set()
>>> b = set()
>>> a.add(b)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'set'
>>> a.add(frozenset(b))
>>> a
set([frozenset([])])
```
`frozenset`構造函數創建給定集合的副本,不管是將集合作為其他集合成員還是字典的鍵,`frozenset`都很有用。
2\. 堆
另外一個眾所周知的數據結構是_堆_(heap),它是優先隊列的一種。使用優先隊列能夠以任意順序增加對象,并且能在任何時間(可能增加對象的同時)找到(也可能是移除)最小的元素,也就是說它比用于列表的`min`方法要有效率得多。
事實上,Python中并沒有獨立的堆類型,只有一個包含一些堆操作函數的模塊,這個模塊叫做`heapq`(`q`是`queue`的縮寫,即隊列),包括6個函數(參見表10-5),其中前4個直接和堆操作相關。你必須將列表作為堆對象本身。
表10-5 heapq模塊中重要的函數
```
heappush(heap, x) 將x入堆
heappop(heap) 將堆中最小的元素彈出
heapify(heap) 將heap屬性強制應用到任意一個列表
heapreplace(heap, x) 將堆中最小的元素彈出,同時將x入堆
nlargest(n, iter) 返回iter中第n大的元素
nsmallset(n, iter) 返回iter中第n小的元素
```
`heappush`函數用于增加堆的項。注意,不能將它用于任何之前講述的列表中,它只能用于通過各種堆函數建立的列表中。原因是元素的順序很重要(盡管看起來是隨意排列,元素并不是進行嚴格排序的)。
```
>>> from heapq import *
>>> from random import shuffle
>>> data = range(10)
>>> shuffle(data)
>>> heap = []
>>> for n in data:
... heappush(heap, n)
...
>>> heap
[0, 2, 1, 6, 5, 3, 4, 9, 7, 8]
>>> heappush(heap, 0.5)
>>> heap
[0, 0.5, 1, 6, 2, 3, 4, 9, 7, 8, 5]
```
元素的順序并不像看起來那么隨意。它們雖然不是嚴格排序的,但是也有規則的:位于`i`位置上的元素總比`i//2`位置處的元素大(反過來說就是`i`位置處的元素總比`2*i`以及`2*i+1`位置處的元素小)。這是底層堆算法的基礎,而這個特性稱為_堆屬性_(heap property)。
`heappop`函數彈出最小的元素,一般來說都是在索引0處的元素,并且會確保剩余元素中最小的那個占據這個位置(保持剛才提到的堆屬性)。一般來說,盡管彈出列表的第一個元素并不是很有效率,但是在這里不是問題,因為`heappop`在“幕后”會做一些精巧的移位操作:
```
>>> heappop(heap)
0
>>> heappop(heap)
0.5
>>> heappop(heap)
1
>>> heap
[2, 5, 3, 6, 7, 8, 4, 9]
```
`heapify`函數使用任意列表作為參數,并且通過盡可能少的移位操作,將其轉換為合法的堆(事實上是應用了剛才提到的堆屬性)。如果沒有用`heappush`建立堆,那么在使用`heappush`和`heappop`前應該使用這個函數。
```
>>> heap = [5, 8, 0, 3, 6, 7, 9, 1, 4, 2]
>>> heapify(heap)
>>> heap
[0, 1, 5, 3, 2, 7, 9, 8, 4, 6]
```
`heapreplace`函數不像其他函數那么常用。它彈出堆的最小元素,并且將新元素推入。這樣做比調用`heappop`之后再調用`heappush`更高效。
```
>>> heapreplace(heap, 0.5)
0
>>> heap
[0.5, 1, 5, 3, 2, 7, 9, 8, 4, 6]
>>> heapreplace(heap, 10)
0.5
>>> heap
[1, 2, 5, 3, 6, 7, 9, 8, 4, 10]
```
`heapq`模塊中剩下的兩個函數`nlargest(n, iter)`和`nsmallest(n, iter)`分別用來尋找任何可迭代對象`iter`中第`n`大或第`n`小的元素。你可以使用排序(比如使用`sorted`函數)和分片來完成這個工作,但是堆算法更快而且更有效第使用內存(還有一個沒有提及的有點:更易用)。
3\. 雙端隊列
雙端隊列(double-ended queue,或稱`deque`)在需要按照元素增加的順序來移除元素時非常有用,Python2.4增加了`collection`模塊,它包括`deque`類型。
_注:Python2.5中的`collections`模塊只包括`deque`類型和`defaultdict`類型,為不存在的鍵提供默認值的字典,未來可能會加入二叉樹(B-Tree)和斐波那契堆(Fibonacci heap)。_
雙端隊列通過可迭代對象(比如集合)創建,而且有些非常有用的方法,如下例所示:
```
>>> from collections import deque
>>> q = deque(range(5))
>>> q.append(5)
>>> q.appendleft(6)
>>> q
deque([6, 0, 1, 2, 3, 4, 5])
>>> q.pop()
5
>>> q.popleft()
6
>>> q.rotate(3)
>>> q
deque([2, 3, 4, 0, 1])
>>> q.rotate(-1)
>>> q
deque([3, 4, 0, 1, 2])
```
雙端隊列好用的原因是它能夠有效的在開頭(左側)增加和彈出元素,這是在列表中無法實現的。除此之外,使用雙端隊列的好處還有:能夠有效地旋轉(rotate)元素(也就是將它們左移或者右移,使頭尾相連)。雙端隊列對象還有`extend`和`extendleft`方法,`extend`和列表的`extend`方法差不多,`extendleft`則類似于`appendleft`。注意,`extendleft`使用的可迭代對象中的元素會反序出現在雙端隊列中。
### 10.3.5 `time`
`time`模塊所包括的函數能夠實現以下功能:獲得當前時間、操作時間和日期、從字符串讀取時間以及格式化時間為字符串。日期可以用實數(從“新紀元”的1月1日0點開始計算到現在的秒數,新紀元是一個與平臺相關的年份,對UNIX來說是1970年),或者是包含有9個整數的元組。這些整數的意義如表10-6所示,比如,元組:
```
(2008, 1, 21, 12, 2, 56, 0, 21, 0)
```
表示2008年1月21日12時2分56秒,星期一,并且是當年的第21天(無夏令時)。
表10-6 Python日期元組的字段含義
```
0 年 比如2000,2001等等
1 月 范圍1~12
2 日 范圍1~31
3 時 范圍0~23
4 分 范圍0~59
5 秒 范圍0~61
6 周 當周一為0時,范圍0~6
7 儒歷日 范圍1~366
8 夏令時 0、1或-1
```
秒的范圍是0~61是為了應付閏秒和雙閏秒。夏令時的數字是布爾值(真或假),但是如果使用了`-1`,`mktime`(該函數將這樣的元組轉換為時間戳,它包含從新紀元開始以來的秒數)就會工作正常。`time`模塊中最重要的函數如表10-7所示。
函數`time.asctime`將當前時間格式化為字符串,如下例所示:
```
>>> time.asctime() 'Fri May 13 17:35:56 2016'
```
表10-7 `time`模塊中重要的函數
```
asctime([tuple]) 將時間元組轉換為字符串
localtime([secs]) 將秒數轉換為日期元組,以本地時間為準
mktime(tuple) 將時間元組轉換為本地時間
sleep(secs) 休眠(不做任何事情)secs秒
strptime(string[, format]) 將字符串解析為時間元組
time() 當前時間(新紀元開始后的描述,以UTC為準)
```
如果不需要使用當前時間,還可以提供一個日期元組(比如通過`localtime`創建的)。(為了實現更精細的格式化,你可以使用`strftime`函數,標準文檔對此有相應的介紹)
函數`time.localtime`將實數(從新紀元開始計算的秒數)轉換為本地時間的日期元組。如果想獲得全球統一時間(有關全球統一時間的更多內容,請參見[http://en/wikipedia.org/wiki/Universal_time](http://en/wikipedia.org/wiki/Universal_time)),則可以使用`gtime`。
函數`time.mktime`將日期元組轉換為從新紀元開始計算的秒數,它與`localtime`的功能相反。
函數`time.sleep`讓解釋器等待給定的秒數。
函數`time.strptime`將`asctime`格式化過的字符串轉換為日期元組(可選的格式化參數所遵循的規則與`strftime`的一樣,詳情請參見標準文檔)。
函數`time.time`使用自新紀元開始計算的秒數返回當前(全球統一)時間,盡管每個平臺的新紀元可能不同,但是你仍然可以通過記錄某事件(比如函數調用)發生前后`time`的結果來對該事件計時,然后計算差值。有關這些函數的實例,請參見下一節的`random`模塊部分。
表10-7列出的函數只是從`time`模塊選出的一部分。該模塊的大多數函數所執行的操作與本小節介紹的內容相類似或者相關。如果需要這里沒有介紹到的函數,請參見[Python庫參考的14.2節](http://python.org/doc/lib/module-time.html),以獲得更多詳細信息。
此外,Python還提供了兩個和時間密切相關的模塊:`datetime`(支持日期和時間的算法)和`timeit`(幫助開發人員對代碼段的執行時間進行計時)。你可以從Python庫參考中找到更多有關它們的信息,第16章也會對`timeit`進行簡短的介紹。
### 10.3.6 `random`
`random`模塊包括返回隨機數的函數,可以用于模擬或者用于任何產生隨機輸出的程序。
_注:事實上,所產生的數字都是偽隨機數,也就是說它們看起來是完全隨機的,但實際上,它們以一個可預測的系統作為基礎。不過,由于這個系統模塊在偽裝隨機方面十分優秀,所以也就不必對此過多擔心了(除非為了實現強加密的目標,因為在這種情況下,這些數字就顯得不夠“強”了,無法抵抗某些特定的攻擊,但是如果你已經深入到強加密的話,也就不用我來解釋這些基礎的問題了)。如果需要真的隨機數,應該使用os模塊的`urandom`函數。`random`模塊內的`SystemRandom`類也是基于同種功能,可以讓數據接近真正的隨機性。_
這個模塊中的一些重要函數如表10-8所示。
表10-8 random模塊中的一些重要的函數
```
random() 返回0<n≤1之間的隨機實數n
getrandbits(n) 以長整型形式返回n個隨機位
uniform(a, b) 返回隨機實數n,其中a≤n<b
randrange([start, ]stop[, step]) 返回range(start, stop, step)中的隨機數
choice(seq) 從序列seq中返回隨意元素
shuffle(seq[, random]) 原地指定序列seq
sample(seq, n) 從序列seq中選擇n個隨機且獨立的元素
```
函數`random.random`是最基本的隨機函數之一,它只是返回0~1的偽隨機數`n`。除非這就是你想要的,否則你應該使用其他提供了額外功能的的函數。`random.getrandbits`以長整型形式返回給定的位數(二進制數)。如果處理的是真正的隨機事務(比如加密),這個函數尤為有用。
為函數`random.uniform`提供兩個數值參數`a`和`b`,它會返回在a~b的隨機(平均分布的)實數n。所以,比如需要隨機數的角度值,可以使用`uniform(0, 360`)。
調用函數`range`可以獲得一個范圍,而使用與之相同的參數來調用標準函數`random.randrange`則能夠產生該范圍內的隨機整數。比如想要獲得1~10(包括10)的隨機數,可以使用`randrange(1, 11)`(或者使用`randrange(10)+1`),如果想要獲得小于20的隨機正奇數,可以使用`randrange(1, 20, 2)`。
函數`random.choice`從給定序列中(均一地)選擇隨機元素。
函數`random.shuffle`將給定(可變)序列的元素進行隨機移位,每種排列的可能性都是近似相等的。
函數`random.sample`從給定序列中(均一地)選擇給定數目的元素,同時確保元素互不相同。
_注:從統計學的角度來說,還有些與`uniform`類似的函數,它們會根據其他各種不同的分布規則進行抽取,從而返回隨機數。這些分布包括貝塔分布、指數分布、高斯分布等等。_
下面介紹一些使用`random`模塊的例子。這些例子將使用一些前文介紹的`time`模塊中的函數。首先獲得代表時間間隔(2008年)限制的實數,這可以通過時間元組的方式來表示日期(使用-1表示一周中的某天,一年中的某天和夏令時,以便讓Python自己計算),并且對這些元組調用`mktime`:
```
>>> from random import *
>>> from time import *
>>> date1 = (2008, 1, 1, 0, 0, 0, -1, -1, -1)
>>> time1 = mktime(date1)
>>> date2 = (2009, 1, 1, 0, 0, 0, -1, -1, -1)
>>> time2 = mktime(date2)
```
然后就能在這個范圍內均一地生成隨機數(不包括上限):
```
>>> random_time = uniform(time1, time2)
```
然后,可以將數字轉換為易讀的日期形式:
```
>>> print asctime(localtime(random_time))
Tue Oct 14 04:33:21 2008
```
在接下來的例子中,我們要求用戶選擇投擲的骰子數以及每個骰子具有的面數。投骰子機制可以由`randrange`和`for`循環實現:
```
#!/usr/bin/env python
# coding=utf-8
from random import randrange
num = input("How many dice? ")
sides = input("How many sides per die? ")
result = 0 for i in range(num):
result += randrange(sides) + 1
print "The result is", result
```
如果將代碼存為腳本文件并且執行,那么會看到下面的交互操作:
```
How many dice? 3
How many sides per die? 6
The result is 11
```
接下來假設有一個新建的文本文件,它的每一行文本都代表一種運勢,那么我們就可以使用前面介紹的`fileinput`模塊將“運勢”都存入列表中,再進行隨機選擇:
```
# fortunu.py
import fileinput, random
fortunes = list(fileinput.input())
print random.choice(fortunes)
```
在UNIX中,可以對標準字典文件`/usr/dict/words`進行測試,以獲得一個隨機單詞:
```
$ python Code.py /usr/dict/words
Greyson
```
最后一個例子,假設你希望程序能夠在每次敲擊回車的時候都為自己發一張牌,同時還要確保不會獲得相同的牌。首先要創建“一副牌”——字符串列表:
```
>>> values = range(1, 11) + "Jack Queen King".split()
>>> suits = "diamonds clubs hearts spades".split()
>>> deck = ["%s of %s" % (v, s) for v in values for s in suits]
```
現在創建的牌還不太適合進行游戲,讓我們來看看現在的牌:
```
>>> from pprint import pprint
>>> pprint(deck[:12])
['1 of diamonds', '1 of clubs', '1 of hearts', '1 of spades', '2 of diamonds', '2 of clubs', '2 of hearts', '2 of spades', '3 of diamonds', '3 of clubs', '3 of hearts', '3 of spades']
```
太整齊了,對吧?不過,這個問題很容易解決:
```
>>> from random import shuffle
>>> shuffle(deck)
>>> pprint(deck[:12])
['7 of hearts', 'Queen of hearts', 'Jack of diamonds', '9 of hearts', '2 of diamonds', '7 of spades', '10 of diamonds', '8 of diamonds', 'Jack of spades', '4 of spades', '2 of clubs', 'King of spades']
```
注意,為了節省空間,這里只打印了前12張牌。你可以自己看看整副牌。
最后,為了讓Python在每次按回車的時候都給你發一張牌,知道發完為止,那么只需要創建一個小的`while`循環即可。假設將建立牌的代碼放在程序文件中,那么只需要在程序的結尾處加入下面這行代碼:
```
while deck:
raw_input(deck.pop())
```
_注:如果在交互式解釋器中嘗試上面找到的`while`循環,那么你會注意到每次按下回車的時候都會打印出一個空字符串。因為`raw_input`返回了輸入的內容(什么都沒有),并且將其打印出來。在一般的程序中,從`raw_input`返回的值都會被忽略掉。為了能夠在交互環節“忽略”它,只需要把`raw_input`的值賦給一些你不想再用到的變量即可。同時將這些變量命名為`ignore`這類名字。_
### 10.3.7 `shelve`
下一章將會介紹如何在文件中存儲數據,但如果只需要一個簡單的存儲方案,那么`shelve`模塊可以滿足你大部分的需要,你所要做的只是為它提供文件名。`shelve`中唯一的有趣的函數是`open`。在調用它的時候(使用文件名作為參數),它會返回一個`shelf`對象,你10.3.7 `shalve`可以用它來存儲內容。只需要把它當做普通的字典(但是鍵一定要作為字符串)來操作即可,在完成工作(并且將內容存儲到磁盤中)之后,調用它的`close`方法。
1\. 潛在的陷阱
`shelve.open`函數返回的對象并不是普通的映射,這一點尤其要注意,如下面的例子所示:
```
>>> import shelve
>>> s = shelve.open("/home/marlowes/workspace/pycharm_Python/Basic_tutorial/test.dat")
>>> s["x"] = ["a", "b", "c"]
>>> s["x"].append("d")
>>> s["x"]
['a', 'b', 'c']
```
`"d"`去哪了?
很容易解釋:當你在`shelf`對象中查找元素的時候,這個對象都會根據已經存儲的版本進行重新構建,當你將元素賦給某個鍵的時候,它就被存儲了。上述例子中執行的操作如下:
? 列表`["a", "b", "c"]`存儲在鍵x下。
? 獲得存儲的表示,并且根據它來創建新的列表,而"d"被添加到這個副本中。修改的版本還沒有被保存!
? 最終,再次獲得原始版本——沒有`"d"`。
為了正確地使用`shelve`模塊修改存儲的對象。必須將臨時變量綁定到獲得的副本上,并且在它被修改后重新存儲這個副本(感謝Luther Blissett指出這個問題):
```
>>> temp = s["x"]
>>> temp.append("d")
>>> s["x"] = temp
>>> s["x"]
['a', 'b', 'c', 'd']
```
Python2.4之后的版本還有個解決方法:將`open`函數的`writeback`參數設為true。如果這樣做,所有從`shelf`讀取或者賦值到`shelf`的數據結構都會保存在內存(緩存)中,并且只有在關閉`shelf`的時候才寫回到磁盤中。如果處理的數據不大,并且不想考慮這些問題,那么將`writeback`設為`true`(確保在最后關閉了`shelf`)的方法還是不錯的。
2\. 簡單的數據庫示例
代碼清單10-8給出了一個簡單的使用shelve模塊的數據庫應用程序。
```
#!/usr/bin/env python
# coding=utf-8
# database.py
import shelve
def store_person(db):
""" Query user for data and store it in the shelf object. """
pid = raw_input("Enter unique ID number: ")
person = {}
person["name"] = raw_input("Enter name: ")
person["age"] = raw_input("Enter age: ")
person["phone"] = raw_input("Enter phone number: ")
db[pid] = person
def lookup_person(db):
""" Query user for ID and desired field, and fetch the correspond data from
the shelf object. """
pid = raw_input("Enter ID number: ")
field = raw_input("What would you like to know? (name, age, phone) ")
field = field.strip().lower() print field.capitalize() + ":", db[pid][field]
def print_help():
print "The available commands are:"
print "store : Store information about a persoon"
print "lookup : Looks up a person from ID number"
print "quit : Save changes and exit"
print "? : Prints this message"
def enter_command():
cmd = raw_input("Enter command(? for help): ")
cmd = cmd.strip().lower()
return cmd
def main():
# You may want to change this name
database = shelve.open("/home/marlowes/workspace/pycharm_Python/Basic_tutorial/database.dat")
try:
while True:
cmd = enter_command()
if cmd == "store":
store_person(database)
elif cmd == "lookup":
lookup_person(database)
elif cmd == "?":
print_help() elif cmd == "quit": return
finally:
database.close()
if __name__ == '__main__':
main()
```
`Database.py`
代碼清單10-8中的程序有一些很有意思的特征。
? 將所有內容都放到函數中會讓程序更加結構化(可能的改進是將函數組織為類的方法)。
? 主程序放在main函數中,只有在`if __name__ == '__main__'`條件成立的時候才被調用。這意味著可以在其他程序中將這個程序作為模塊導入,然后調用`main`函數。
? 我在`main`函數中打開數據庫(`shelf`),然后將其作為參數傳給另外需要它的函數。當然,我也可以使用全局變量,畢竟這個程序很小。不過,在大多數情況下最好避免使用全局變量,除非有充足的理由要使用它。
? 在一些值中進行讀取之后,對讀取的內容調用`strip`和`lower`函數以生成了一個修改后的版本。這么做的原因在于:如果提供的鍵與數據庫存儲的鍵相匹配,那么它們應該完全一樣。如果總是對用戶的輸入使用`strip`和`lower`函數,那么就可以讓用戶隨意輸入大小寫字母和添加空格了。同時需要注意的是:在打印字段名稱的時候,我使用了`capitalize`函數。
? 我使用`try/finally`確保數據庫能夠正確關閉。我們永遠不知道什么時候會出錯(同時程序會拋出異常)。如果程序在沒有正確關閉數據庫的情況下終止,那么,數據庫文件就有可能被損壞了,這樣的數據文件是毫無用處的。使用`try/finally`就可以避免這種情況了。
接下來,我們測試一下這個數據庫。下面是一個簡單的交互過程:
```
Enter command(? for help): ?
The available commands are:
store : Store information about a persoon
lookup : Looks up a person from ID number
quit : Save changes and exit
? : Prints this message
Enter command(? for help): store
Enter unique ID number: 001 Enter name: Greyson
Enter age: 19 Enter phone number: 001-160309 Enter command(? for help): lookup
Enter ID number: 001 What would you like to know? (name, age, phone) phone
Phone: 001-160309 Enter command(? for help): quit
```
交互的過程并不是十分有趣,使用普通的字典也能獲得和`shelf`對象一樣的效果。但是,我們現在退出程序,然后再重新啟動它,看看發生了什么?也許第二天才重新啟動它:
```
Enter command(? for help): lookup
Enter ID number: 001 What would you like to know? (name, age, phone) name
Name: Greyson
Enter command(? for help): quit
```
我們可以看到,程序讀出了第一次創建的文件,而Greyson的資料還在!
你可以隨意試驗這個程序,看看是否還能擴展它的功能并且提高用戶友好度。你是不是想創建一個供自己使用的版本?創建一個唱片集的數據庫怎樣?或者創建一個數據庫,幫助自己記錄借書朋友的名單(我想我會用這個版本)。
### 10.3.8 `re`
有些人面臨一個問題時回想:“我知道,可以使用正則表達式來解決這個問題。”于是現在他們就有兩個問題了。 ——Jamie Zawinski(Lisp黑客,Netscape早期開發者。關于他的更詳細編程生涯,可見人民郵電出版社出版的《編程人生》一書)
`re`模塊包含對_正則表達式_(regular expression)的支持。如果你之前聽說過正則表達式,那么你可能知道它有多強大了,如果沒有,請做好心里準備吧,它一定會令你很驚訝。
但是應該注意,在學習正則表達式之初會有點困難(好吧,其實是很難)。學習它們的關鍵是一次只學習一點——(在文檔中)查找滿足特定任務需要的那部分內容,預先將它們全部記住是沒必要的。本章將會對`re`模塊主要特征和正則表達式進行介紹,以便讓你上手。
_注:除了標準文檔外,Andrew Kuchling的["Regular Expression HOWTO"(正則表達式HOWTO)](http://amk.ca/python/howto/regex/)也是學習在Python中使用正則表達式的有用資源。_
1.什么是正則表達式
正則表達式是可以匹配文本片段的模式。最簡單的正則表達式就是普通字符串,可以匹配其自身。換句話說,正則表達式"python"可以匹配字符串"python"。你可以用這種匹配行為搜索文本中的模式,并且用計算后的值替換特定模式,或者將文本進行分段。
○ 通配符
正則表達式可以可以匹配多于一個的字符串,你可以使用一些特殊字符串創建這類模式。比如點號(`.`)可以匹配任何字符(除了換行符),所以正則表達式`".ython"`可以匹配字符串`"python"`和`"jython"`。它還能匹配`"qython"`、`"+ython"`或者`" ython"`(第一個字母是空格),但是不會匹配`"cpython"`或者`"ython"`這樣的字符,因為點號只能匹配一個字母,而不是兩個或者零個。
因為它可以匹配“任何字符串”(除換行符外的任何單個字符),點號就稱為_通配符_(wildcard)。
○?對特殊字符進行轉義
你需要知道:在正則表達式中如果將特殊字符作為普通字符使用會遇到問題,這很重要。比如,假設需要匹配字符串`"python.org"`,直接調用`"python.org"`可以么?這么做是可以的,但是這樣也會匹配`"pythonzorg"`,這可不是所期望的結果(點號可以匹配除換行符外的任何字符,還記得吧)。為了讓特殊字符表現得像普通字符一樣,需要對它進行_轉義_(escape),就像我在第1章中對引號進行轉義所做的一樣——可以在它前面加上反斜線。因此,在本例中可以使用`"python\\.org"`,這樣就只會匹配`"python.org"`了。
_注:為了獲得`re`模塊所需的單個反斜線,我們要在字符串中使用兩個反斜線——為了通過解釋器進行轉義。這樣就需要兩個級別的轉義了:(1)通過解釋器轉義;(2)通過re模塊轉義(事實上,有些情況下可以使用單個反斜線,讓解釋器自動進行轉義,但是別依賴這種功能)。如果厭煩了使用雙斜線,那么可以使用原始字符串,比如`r"python\.org"`。_
○?字符集
匹配任意字符可能很有用,但有些時候你需要更多的控制權。你可以使用中括號括住字符串來創建_字符集_(character set)。字符集可以匹配它所包括的任意字符,所以`"[pj]ython"`能夠匹配`"python"`和`"jython"`,而非其他內容。你可以使用范圍,比如`"[a-z]"`能夠(按字母順序)匹配`a`到`z`的任意一個字符,還可以通過一個接一個的方式將范圍聯合起來使用,比如`"[a-zA-Z0-9]"`能夠匹配任意大小寫字母和數字(注意字符集只能匹配一個這樣的字符)。
為了反轉字符集,可以在開頭使用^字符,比如`"[^abc]"`可以匹配任何除了`a`、`b`和`c`之外的字符。
**字符集中的特殊字符**
一般來說,如果希望點號、星號和問號等特殊字符在模式中用作文本字符而不是正則表達式運算符,那么需要用反斜線進行轉義。在字符集中,對這些字符進行轉義通常是沒必要的(盡管是完全合法的)。不過,你應該記住下面的規則:
? 如果脫字符(`^`)出現在字符集的開頭,那么你需要對其進行轉義了,除非希望將它用做否定運算符(換句話說,不要將它放在開頭,除非你希望那樣用);
? 同樣,右中括號(`]`)和橫線(`-`)應該放在字符集的開頭或者用反斜線轉義(事實上,如果需要的話,橫線也能放在末尾)。
○ 選擇符和子模式
在字符串的每個字符都有各不相同的情況下,字符集是很好用的,但如果只想匹配字符串`"python"`和`"perl"`呢?你就不能使用字符集或者通配符來指定某個特定的模式了。取而代之的是用于選擇項的特殊字符:管道符號(|)。因此,所需的模式可以寫成`"python|perl"`。
但是,有些時候不需要對整個模式使用選擇運算符,只是模式的一部分。這時可以使用圓括號括起需要的部分,或稱子模式(subparttern)。前例可以寫成`"p(ython|erl)"`。(注意,術語_子模式_也是適用于單個字符)
○ 可選項和可重復子模式
在子模式后面加上問號,它就變成了可選項。它可能出現在匹配字符串中,但并非必需的。例如,下面這個(稍微有點難懂)模式:
```
r"(http://)?(www\.)?python\.org"
```
只能匹配下列字符串(而不會匹配其他的):
```
"http://www.python.org"
"http://python.org"
"www.python.org"
"python.org"
```
對于上述例子,下面這些內容是值得注意的:
? 對點號進行了轉義,防止它被作為通配符使用;
? 使用原始字符串減少所需反斜線的數量;
? 每個可選子模式都用圓括號括起;
? 可選子模式出現與否均可,而且互相獨立。
問號表示子模式可以出現一次或根本不出現,下面這些運算符允許子模式重復多次:
? `(pattern)*`:允許模式重復0次或多次;
? `(pattern)+`:允許模式重復1次或多次;
? `(patten){m,n}`:允許模式重復m~n次。
例如,`r"w*\.python\.org"`會匹配`"www.python.org"`,也會匹配`".python.org"`、`"ww.python.org"`和`"wwwwww.python.org"`。類似地,`r"w+\.python\.org"`匹配`"w.python.org"`但不匹配`".python.org"`,而`r"w{3,4}\.python\.org"`只匹配`"www.python.org"`和`"wwww.python.org"`。
_注:這里使用術語匹配(match)表示模式匹配整個字符串。而接下來要說到的match函數(參見表10-9)只要求模式匹配字符串的開始。_
○ 字符串的開始和結尾
目前為止,所出現的模式匹配都是針對整個字符串的,但是也能尋找匹配模式的子字符串,比如字符串`"www.python.org"`中的子字符串`"www"`能夠匹配模式`"w+"`。在尋找這樣的子字符串時,確定子字符串位于整個字符串的開始還是結尾是很有用的。比如,只想在字符串的開頭而不是其他位置匹配`"ht+p"`,那么就可以使用脫字符(`^`)標記開始:`"^ht+p"`會匹配`"http://python.org"`(以及`"httttp://python.org"`),但是不匹配`"www.python.org"`。類似的,字符串結尾用美元符號(`$`)標識。
_注:有關正則表達式運算符的完整列表,請參見Python類參考的[4.2.1節的內容](http://python.org/doc/lib/re-syntax.html)。_
2.`re`模塊的內容
如果不知道如何應用,只知道如何書寫正則表達式還是不夠的。`re`模塊包含一些有用的操作正則表達式的函數。其中最重要的一些函數如表10-9所示。
表10-9 `re`模塊中一些重要的函數
```
compile(pattern[, flags]) 根據包含正則表達式的字符串創建模式對象
search(pattern, string[, flags]) 在字符串中尋找模式
match(pattern, string[, flags]) 在字符串的開始處匹配模式
split(pattern string[, maxsplit=0]) 根據模式的匹配項來分割字符串
findall(pattern, string) 列出字符串中模式的所有匹配項
sub(pat, repl, string[, count=0]) 將字符串中所有pat的匹配項用repl替換
escape(string) 將字符串中所有特性正則表達式字符轉義
```
函數`re.compile`將正則表達式(以字符串書寫的)轉換成模式對象,可以實現更有效率的匹配。如果在調用`search`或者`match`函數的時候使用字符串表示的正則表達式,它們也會在內部將字符串轉換為正則表達式對象。使用`compile`完成一次轉換之后,在每次使用模式的時候就不用進行轉換。模式對象本身也沒有查找/匹配的函數,就像方法一樣,所以`re.search(pat, string)`(`pat`是用字符串表示的正則表達式)等價于`pat.search(string)`(`pat`是用`compile`創建的模式對象)。經過`compile`轉換的正則表達式對象也能用于普通的`re`函數。
函數`re.search`會在給定字符串中尋找第一個匹配給定正則表達式的子字符串。一旦找到子字符串,函數就會返回`MatchObject`(值為`True`),否則返回`None`(值為`False`)。因為返回值的性質,所以該函數可以用在條件語句中,如下例所示:
```
if re.search(pat, string): print "Found it!"
```
同時,如果需要更多有關匹配的子字符串的信息,那么可以檢查返回的`MatchObject`對象(有關`MatchObject`更多的內容,請參見下一節)。
函數`re.match`會在給定字符串的開頭匹配正則表達式。因此,`match("p", "python")`返回真(即匹配對象`MatchObject`),而`re.match("p", "www.python.org")`則返回假(`None`)。
_注:如果模式與字符串的開始部分相匹配,那么`match`函數會給出匹配的結果,而模式并不需要匹配整個字符串。如果要求模式匹配整個字符串,那么可以在模式的結尾加上美元符號。美元符號會對字符串的末尾進行匹配,從而“順延”了整個匹配。_
函數`re.split`會根據模式的匹配項來分割字符串。它類似于字符串方法`split`,不過是用完整的正則表達式替代了固定的分隔符字符串。比如字符串方法`split`允許用字符串`","`的匹配項來分割字符串,而`re.split`則允許用任意長度的逗號和空格序列來分割字符串:
```
>>> import re
>>> some_text = "alpha, beta,,,,gamma delta"
>>> re.split("[, ]+", some_text)
['alpha', 'beta', 'gamma', 'delta']
```
_注:如果模式包含小括號,那么括起來的字符組合會散布在分割后的子字符串之間。例如,`re.split("o(o)", "foobar")`回生成`["f", "o", "bar"]`。_
從上述例子可以看到,返回值是子字符串的列表。`maxsplit`參數表示字符串最多可以分割的次數:
```
>>> re.split("[, ]+", some_text, maxsplit=2)
['alpha', 'beta', 'gamma delta']
>>> re.split("[, ]+", some_text, maxsplit=1)
['alpha', 'beta,,,,gamma delta']
```
函數`re.findall`以列表形式返回給定模式的所有匹配項。比如,要在字符串中查找所有的單詞,可以像下面這么做:
```
>>> pat = "[a-zA-Z]+"
>>> text = '"Hm... Err -- are you sure?" he said, sounding insecure.'
>>> re.findall(pat, text)
['Hm', 'Err', 'are', 'you', 'sure', 'he', 'said', 'sounding', 'insecure']
```
或者查找標點符號:
```
>>> pat = r'[.?\-",]+'
>>> re.findall(pat, text)
['"', '...', '--', '?"', ',', '.']
```
注意,橫線(`-`)被轉義了,所以Python不會將其解釋為字符范圍的一部分(比如a~z)。
函數`re.sub`的作用在于:使用給定的替換內容將匹配模式的子字符串(最左端并且非重疊的子字符串)替換掉。請思考下面的例子:
```
>>> pat = '{name}'
>>> text = 'Dear {name}...'
>>> re.sub(pat, "Mr. Greyson", text) 'Dear Mr. Greyson...'
```
請參見本章后面“作為替換的組號和函數”部分,該部分會向你介紹如何更有效地使用這個函數。
`re.escape`是一個很實用的函數,它可以對字符串中所有可能被解釋為正則運算符的字符進行轉義的應用函數。如果字符串很長且包含很多特殊字符,而你又不想輸入一大堆反斜線,或者字符串來自于用戶(比如通過`raw_input`函數獲取的輸入內容),且要用作正則表達式的一部分的時候,可以使用這個函數。下面的例子向你演示了該函數是如何工作的:
```
>>> re.escape("www.python.org")
'www\\.python\\.org'
>>> re.escape("But where is the ambiguity?")
'But\\ where\\ is\\ the\\ ambiguity\\?'
```
_注:你可能會注意到,表10-9中有些函數包含了一個名為`flags`的可選參數。這個參數用于改變解釋正則表達式的方法。有關它的更多信息,請參見[Python庫參考的4.2節](http://python.org/doc/lib/module-re.html) 。這個標志在4.2.3節中有介紹。_
3.匹配對象和組
對于`re`模塊中那些能夠對字符串進行模式匹配的函數而言,當能找到匹配項的時候,它們都會返回`MatchObject`對象。這些對象包括匹配模式的子字符串的信息。它們還包含了那個模式匹配了子字符串哪部分的信息——這些“部分”叫做組(group)。
簡而言之,組就是放置在圓括號內的子模式。組的序號取決于它左側的括號數。組0就是整個模式,所以在下面的模式中:
```
"There (was a (wee) (cooper)) who (lived in Fyfe)"
```
包含下面這些組:
```
0 There was a wee cooper who lived in Fyfe
1 was a wee cooper
2 wee
3 cooper
4 lived in Fyfe
```
一般來說,如果組中包含諸如通配符或者重復運算符之類的特殊字符,那么你可能會對是什么與給定組實現了匹配感興趣,比如在下面的模式中:
```
r"www\.(.+)\.com$"
```
組0包含整個字符串,而組1則包含位于`"www."`和`".com"`之間的所有內容。像這樣創建模式的話,就可以取出字符串中感興趣的部分了。
`re`匹配對象的一些重要方法如表10-10所示。
表10-10 re匹配對象的重要方法
```
group([group1, ...]) 獲取給定子模式(組)的匹配項
start([group]) 返回給定組的匹配項的開始位置
end([group]) 返回給定組的匹配項的結束位置(和分片不一樣,不包括組的結束位置)
span([group]) 返回一個組的開始和結束位置
```
`group`方法返回模式中與給定組匹配的(子)字符串。如果沒有給出組號,默認為組0。如果給定一個組號(或者只用默認的0),會返回單個字符串。否則會將對應給定組數的字符串作為元組返回。
_注:除了整體匹配外(組0),我們只能使用99個組,范圍1~99。_
`start`方法返回給定組匹配項的開始索引(默認為0,即整個模式)。
方法`end`類似于`start`,但是返回結果是結束索引加1。
方法`span`以元組`(start,end)`的形式返回給定組的開始和結束位置的索引(默認為0,即整個模式)。
請思考以下例子:
```
>>> m = re.match(r"www\.(.*)\..{3}", "www.python.org")
>>> m.group(1)
'python'
>>> m.start(1)
4
>>> m.end(1)
10
>>> m.span(1)
(4, 10)
```
4\. 作為替換的組號和函數
在使用`re.sub`的第一個例子中,我只是把一個字符串用其他的內容替換掉了。我用`replace`這個字符串方法(3.4節對此進行了介紹)能輕松達到同樣的效果。當然,正則表達式很有用,因為它們允許以更靈活的方式搜索,同時它們也允許進行功能更強大的替換。
見證`re.sub`強大功能的最簡單方式就是在替換字符串中使用組號。在替換內容中以`"\\n"`形式出現的任何轉義序列都會被模式中與組n匹配的字符串替換掉。例如,假設要把`"*something*"`用`"<em>something</em>"`替換掉,前者是在普通文本文檔(比如Emaill)中進行強調的常見方法,而后者則是相應的HTML代碼(用于網頁)。我們首先建立正則表達式:
```
>>> emphasis_pattern = r"\*([^\*]+)\*"
```
注意,正則表達式很容易變得難以理解,所以為了讓其他人(包括自己在內)在以后能夠讀懂代碼,使用有意義的變量名(或者加上一兩句注釋)是很重要的:
注:讓正則表達式變得更加易讀的方式是在`re`函數中使用`VERBOSE`標志。它允許在模式中添加空白(空白字符、`tab`、換行符,等等),`re`則會忽略它們,除非將其放在字符類或者用反斜線轉義。也可以在冗長的正則式中添加注釋。下面的模式對象等價于剛才寫的模式,但是使用了`VERBOSE`標志:
```
>>> emphasis_pattern = re.compile(r'''
... \* # Beginning emphasis tag -- an asterisk
... ( # Begin group for capturing phrase
... [^\*]+ # Capture anything except asterisks
... ) # End group
... \* # Ending emphasis tag
... ''', re.VERBOSE)
```
現在模式已經搞定,接下來就可以使用re.sub進行替換了:
```
>>> re.sub(emphasis_pattern, r"<em>\1</em>", "Hello, *world*!")
'Hello, <em>world</em>!'
```
從上述例子可以看到,普通文本已經成功地轉換為HTML。
將函數作為替換內容可以讓替換功能變得更加強大。`MatchObject`將作為函數的唯一參數,返回的字符串將會用做替換內容。換句話說,可以對匹配的子字符串做任何事,并且可以細化處理過程,以生成替換內容。你可能會問,這個功能用在什么地方呢?開始使用正則表達式以后,你肯定會發現這個功能的無數應用。本章后面的“模板系統示例”部分會向你介紹它的一個應用。
**貪婪和非貪婪模式**
重復運算符默認是貪婪(greedy)的,這意味著它會進行盡可能多的匹配。比如,假設我重寫了剛才用到的程序,以使用下面的模式:
```
>>> emphasis_pattern = r"\*(.+)\*"
```
它會匹配星號加上一個或多個字符,再加上一個星號的字符串。聽起來很完美吧?但實際上不是:
```
>>> re.sub(emphasis_pattern, r"<em>\1</em>", "*This* is *it*!")
'<em>This* is *it</em>!'
```
模式匹配了從開始星號到結束星號之間的所有內容——包括中間的兩個星號!也就意味著它是貪婪的:將盡可能多的東西都據為己有。
在本例中,你當然不希望出現這種貪婪行為。當你知道某個特定字母不合法的時候,前面的解決方案(使用字符集匹配任何不是星號的內容)才是可行的。但是假設另外一種情況:如果使用`"**something**"`表示強調呢?現在在所強調的部分包括單個星號已經不是問題了,但是如何避免過于貪婪?
事實上非常簡單,只要使用重復運算符的非貪婪版本即可。所有的重復運算符都可以通過在其后面加上一個問號變成非貪婪版本:
```
>>> emphasis_pattern = r"\*\*(.+?)\*\*"
>>> re.sub(emphasis_pattern, r"<em>\1</em>", "**This** is **it**!")
'<em>This</em> is <em>it</em>!'
```
這里用`+?`運算符代替了`+`,意味著模式也會像之前那樣隊一個或者多個通配符進行匹配,但是它會進行盡可能少的匹配,因為它是非貪婪的。它僅會在到達`"\*\*"`的下一個匹配項之前匹配最少的內容——也就是在模式的結尾進行匹配。我們可以看到,代碼工作得很好。
5\. 找出Email的發信人
有沒有嘗試過將Email存為文本文件?如果有的話,你會看到文件的頭部包含了一大堆與郵件內容無關的信息,如代碼清單10-9所示。
```
#代碼清單10-9 一組(虛構的)Email頭部信息
From foo@bar.baz Thu Dec 20 01:22:50 2008 Return-Path: <foo@bar.baz> Received: from xyzzy42.bar.com (xyzzy.bar.baz [123.456.789.42])
by frozz.bozz.floop (8.9.3/8.9.3) with ESMTP id BAA25436 for <maguns@bozz.floop>: Thu 20 Dec 2004 01:22:50 +0100 (MET)
Received: from [43.253.124.23] by bar.baz
[InterMail vM.4.01.03.27 201-229-121-20010626] with ESMTP
id <20041220002242.ADASD123.bar.baz@[43.253.124.23]>:
Thu, 20 Dec 2004 00:22:42 +0000 User-Agent: Microsot-Outlook-Express-Macintosh-Edition/5.02.2022 Date: Wed, 19 Dec 2008 17:22:42 -0700 Subject: Re: Spam
From: Foo Fie <foo@bar.baz> To: Magnus Lie Hetland <magnus@bozz.floop> CC: <Mr.Gumby@bar.baz> Message-ID: <B8467D62.84F%foo@baz.com> In-Reply-To: <20041219013308.A2655@bozz.floop> Mime-version: 1.0 Content-type: text/plain: charset="US-ASCII" Content-transfer-encoding: 7bit
Status: RO
Content-Length: 55 Lines: 6 So long, and thanks for all the spam!
Yours.
Foo Fie
```
我們試著找出這封Email是誰發的。如果直接看文本,你肯定可以指出本例中的發信人(特別是查看郵件結尾簽名的話,那就更直接了)。但是能找出通用的模式嗎?怎么能把發信人的名字取出而不帶著Email地址呢?或者如何將頭部信息中包含的Email地址列示出來呢?我們先處理第一個任務。
包含發信人的文本行以字符串`"From:"`作為開始,以放置在尖括號(`<`和`>`)中的Email地址作為結束。我們需要的文本就夾在中間。如果使用`fileinput`模塊,那么這個需求就很容易實現了。代碼清單10-10給出了解決這個問題的程序。
_注:這個問題也可以不使用正則表達式解決,可以使用`email`模塊。_
```
# 代碼清單10-10 尋找Email發信人的程序
# RegularExpression.py
import fileinput import re
pat = re.compile(r"From: (.*) <.*?>$")
for line in fileinput.input():
m = pat.match(line) if m: print m.group(1)
```
可以像下面這樣運行程序(假設郵件內容存儲在文本文件`message.eml`中):
```
$ python RegularExpression.py message.eml
Foo Fie
```
對于這個程序,應該注意以下幾點:
? 我用`compile`函數處理了正則表達式,讓處理過程更有效率;
? 我將需要取出的子模式放在圓括號中作為組;
? 我使用非貪婪模式對郵件地址進行匹配,那么只有最后一對尖括號符合要求(當名字包含了尖括號的情況下);
? 我使用了美元符號表明我要匹配正行;
? 我使用if語句確保在我試圖從特定組中取出匹配內容之前,的確進行了匹配。
為了列出頭部信息中所有的Email地址,需要建立只匹配Email地址的正則表達式。然后可以使用`findall`方法尋找每行出現的匹配項。為了避免重復,可以將地址保存在集合中(本章前面介紹過)。最后,取出所有的鍵,排序,并且打印出來:
```
import re import fileinput
pat = re.compile(r"[a-z\-\.]+@[a-z\-\.]+", re.IGNORECASE)
addresses = set()
for line in fileinput.input():
for address in pat.findall(line):
addresses.add(address)
for address in sorted(addresses):
print address
```
運行程序的時候會輸出如下結果(以代碼清單10-9的郵件信息作為輸入):
```
Mr.Gumby@bar.baz
foo@bar.baz
foo@baz.com
magnus@bozz.floop
```
_注:在這里,我并沒有嚴格照著問題規范去做。問題的要求是在頭部找出Email地址,但是這個程序找出了整個文件中的地址。為了避免這種情況,如果遇到空行就可以調用`fileinput.close()`,因為頭部不包含空行,遇到空行就證明工作完成了。此外,你還可以使用`fileinput.nextfile()`開始處理下一個文件——如果文件多于一個的話。_
6\. 模板系統示例
_模板_是一種通過放入具體值從而得到某種已完成文本的文件。比如,你可能會有只需要插入收件人姓名的郵件模板。Python有一種高級的模板機制:字符串格式化。但是使用正則表達式可以讓系統更加高級。假設需要把所有`"[somethings]"`(字段)的匹配項替換為通過Python表達式計算出來的`something`結果,所以下面的字符串:
```
"The sum of 7 and 9 is [7 + 9]."
```
應該被翻譯為如下形式:
```
"The sum of 7 and 9 is 16."
```
同時,還可以在字段內進行賦值,所以下面的字符串:
```
"[name='Mr. Gumby']Hello, [name]"
```
應該被翻譯為如下形式:
```
"Hello, Mr. Gumby"
```
看起來像是復雜的工作,但是我們再看一下可用的工具。
? 可以使用正則表達式匹配字段,提取內容。
? 可以用`eval`計算字符值,提供包含作用域的字典。可以在`try/except`語句內進行這項工作。如果引發了`SyntaxError`異常,可能是某些語句出現了問題(比如賦值),應該使用`exec`來代替。
? 可以用`exce`執行字符串(和其他語句)的賦值操作,在字典中保存模板的作用域。
? 可以使用`re.sub`將求值的結果替換為處理后的字符串。
這樣看來,這項工作又不再讓人寸步難行了,對吧?
_注:如果某項任務令人望而卻步,將其分解為小一些的部分總是有用的。同時,要對解決問題所使用的工具進行評估。_
代碼清單10-11是一個簡單的實現。
```
#!/usr/bin/env python # coding=utf-8
# templates.py
import re import fileinput
# Matching in brackets in the field.
filed_pat = re.compile(r"\[(.+?)\]")
# We will be variable collected here
scope = {}
# Used in the re.sub.
def replacement(math):
code = math.group(1)
try:
# If the field can be evaluated, then return it.
return str(eval(code, scope))
except SyntaxError:
# Otherwise the same scope of assignment statements.
exec code in scope
# Return an empty string.
return ""
# All text in the from of a string.
# There are other ways, see chapter 11.
lines = []
for line in fileinput.input():
lines.append(line)
text = "".join(lines)
# Replace all field pattern match.
print filed_pat.sub(replacement, text)
```
Templates.py
簡單來說,程序做了下面的事情。
? 定義了用于匹配字段的模式。
? 創建充當模板作用域的字典。
? 定義具有下列功能的替換函數。
* 將組1從匹配中取出,放入`code`中;
* 通過將作用域字典作為命名空間來對`code`進行求值,將結果轉換為字符串返回,如果成功的話。字段就是個表達式,一切正常。否則(也就是引發了`SyntaxError`異常),跳到下一步;
* 執行在相同命名空間(作用域字典)內的字段來對表達式求值,返回空字符串(因為賦值語句沒有任何內容進行求值)。
? 使用`fileinput`讀取所有可用的行,將其放入列表,組合成一個大字符串。
? 將所有`field_pat`的匹配項用`re.sub`中的替換函數進行替換,并且打印結果。
_注:在之前的Python中,將所有行放入列表,最后再聯合要比下面這種方法更有效率:_
```
text = ""
for line in fileinput.input():
text += line
```
_盡管看起來很優雅,但是每個賦值語句都要創建新的字符串,由舊的字符串和新增加字符串聯結在一起組成,這樣就會造成嚴重的資源浪費,使程序運行緩慢。在舊版本的Python中,使用`join`方法和上述做法之間的差異是巨大的。但是在最近的版本中,使用`+=`運算符事實上會更快。如果覺得性能很重要,那么你可以嘗試這兩種方式。同時,如果需要一種更優雅的方式來讀取文件的所有文本,那么請參見第十一章。_
好了,我只用15行代碼(不包括空行和注釋)就創建了一個強大的模板系統。希望讀者已經認識到:使用標準庫的時候,Python有多么強大。下面,我們通過測試這個模板系統來結束本例。試著對代碼清單10-12中的示例文本運行該系統。
```
# 代碼清單10-12 簡單的模板示例
[x = 2]
[y = 3]
The sum of [x] and [y] is [x + y].
```
應該會看到如下結果:
```
The sum of 2 and 3 is 5.
```
_注:雖然看起來不明顯,但是上面的輸出包含了3個空行——兩個在文本上方,一個在下方。盡管前兩個字段已經被替換為空字符串,但是隨后的空行還留在那里。同時,`print`語句增加了新行,也就是末尾的空行。_
但是等等,它還能更好!因為使用了`fileinput`,我可以輪流處理幾個文件。這意味著可以使用一個文件為變量定義值,而另一個文件作為插入這些值的模板。比如,代碼清單10-13包含了定義文件,名為`magnus.txt`,而代碼清單10-14則是模板文件,名為`template.txt`。
```
# 代碼清單 10-13 一些模板定義
[name = "Magnus Lie Hetland"]
[email = "magnus@foo.bar"]
[language = "python"]
# 代碼清單 10-14 一個模板
[import time]
Dear [name].
I would like to learn how to program. I hear you use
the [language] language a lot -- is it something I should consider?
And, by the way, is [email] your correct email address?
Fooville, [time.asctime()]
Oscar Frozzbozz
```
`import time`并不是賦值語句(而是準備處理的語句類型),但是因為我不是過分挑剔的人,所以只用了`try/except`語句,使得程序支持任何可以配合`eval`或`exec`使用的語句和表達式。可以像下面這樣運行程序(在UNIX命令行下):
```
$ python templates.py magnus.txt template.txt
```
你將會看到類似以下內容的輸出:
```
Dear Magnus Lie Hetland.
I would like to learn how to program. I hear you use
the python language a lot -- is it something I should consider?
And, by the way, is magnus@foo.bar your correct email address?
Fooville, Wed May 18 20:58:58 2016 Oscar Frozzbozz
```
盡管這個模板系統可以進行功能非常強大的替換,但它還是有些瑕疵的。比如,如果能夠使用更靈活的方式來編寫定義文件就更好了。如果使用`execfile`來執行文件,就可以使用正常的Python語法了。這樣也會解決輸出內容中頂部出現空行的問題。
還能想到其他改進的方法嗎?對于程序中使用的概念,還能想到其他用途嗎?精通任何程序設計語言的最佳方法是實踐——測試它的限制,探索它的威力。看看你能不能重寫這個程序,讓它工作得更好并且更能滿足需求。
_注:事實上,在標準庫的`string`模塊中已經有一個非常完美的模板系統了。例如,你可以了解一下`Template`類。_
### 10.3.9 其他有趣的標準模塊
盡管本章內容已經涵蓋了很多模塊,但是對于整個標準庫來說這只是冰山一角。為了引導你進行深入探索,下面會快速介紹一些很酷的庫。
? `functools`:你可以從這個庫找到一些功能,讓你能夠通過部分參數來使用某個參數(部分求值),稍后再為剩下的參數提供數值。在Python3.0中,`filter`和`reduce`包含在該模塊中。
? `difflib`:這個庫讓你可以計算兩個序列的相似度。還能讓你從一些序列中(可供選擇的序列列表)找出提供的原始序列“最像”的那個。`difflib`可以用于創建簡單的搜索程序。
? `hashlib`:通過這個模塊,你可以通過字符串計算小“簽名”(數字)。如果為兩個不同的字符串計算出了簽名,幾乎可以確保這兩個簽名完全不同。該模塊可以應用與大文本文件,同時在加密和安全性(另見`md5`和`sha`模塊)方面有很多用途。
? `csv`:CSV是逗號分隔值(Comma-Separated Values)的簡寫,這是一種很多程序(比如很多電子表格和數據庫程序)都可以用來存儲表格式數據的簡單格式。它主要用于在不同程序間交換數據。使用`csv`模塊可以輕松讀寫CSV文件,同時以顯而易見的方式來處理這種格式的某些很難處理的地方。
? `timeit`、`profile`和`trace`:`time`模塊(以及它的命令行腳本)是衡量代碼片段運行時間的工具。它有很多神秘的功能,你應該用它來代替`time`模塊進行性能測試。`profil`e模塊(和伴隨模塊pstats)可用于代碼片段效率的全面分析。`trace`模塊(和程序)可以提供總的分析(也是代碼哪部分執行了,哪部分沒執行)。這在寫測試代碼的時候很有用。
? `datetime`:如果`time`模塊不能滿足時間追蹤方面的需求,那么`datetime`可能就有用武之地了。它支持特殊的日期和時間對象,讓你能夠以多種方式對它們進行構建和聯合。它的接口在很多方面比`time`的接口要更加直觀。
? `itertools`:它有很多工具用來創建和聯合迭代器(或者其他可迭代對象),還包括實現以下功能的函數:將可迭代的對象鏈接起來、創建返回無限連續整數的迭代器(和`range`類似,但是沒有上限),從而通過重復訪問可迭代對象進行循環等等。
? `logging`:通過簡單的`print`語句打印出程序的哪些方面很有用。如果希望對程序進行跟蹤但又不想打印出太多調試內容,那么就需要將這些信息寫入日志文件中了。這個模塊提供了一組標準的工具,以便讓開發人員管理一個或多個核心的日志文件,同時還對日志信息提供了多層次的優先級。
? `getopt`和`optparse`:在UNIX中,命令行程序經常使用不同的_選項_(option)或者_開關_(switches)運行(Python解釋器就是個典型的例子)。這些信息都可以在`sys.argv`中找到,但是自己要正確處理它們就沒有這么簡單了。針對這個問題,`getopt`庫是個切實可行的解決方案,而`optparse`則更新、更強大并且更易用。
? `cmd`:使用這個模塊可以編寫命令行解釋器,就像Python的交互式解釋器一樣。你可以自定義命令,以便讓用戶能夠通過提示符來執行。也許你還能將它作為程序的用戶界面。
## 10.4 小結
本章講述了模塊的知識:如何創建、如何探究以及如何使用標準Python庫中的模塊。
? 模塊:從基本上來說,模塊就是子程序,它的主函數則用于定義,包括定義函數、類和變量。如果模塊包含測試代碼,那么應該將這部分代碼放置在檢查 `__name__ == '__main__'`是否為真的if語句中。能夠在`PYTHONPATH`中找到的模塊都可以導入。語句`import foo`可以導入存儲在`foo.py`文件中的模塊。
? 包:包是包含有其他模塊的模塊。包是作為包含`__init__.py`文件的目錄來實現的。
? 探究模塊:將模塊導入交互式編輯器后,可以用很多方法對其進行探究。比如使用`dir`檢查`__all__`變量以及使用`help`函數。文檔和源碼是獲取信息和內部機制的極好來源。
? 標準庫:Python包括了一些模塊,總稱為標準庫。本章講到了其中的很多模塊,以下對其中一部分進行回顧。
??? ○ `sys`:通過該模塊可以訪問到多個和Python解釋器聯系緊密的變量和函數。
??? ○ `os`:通過該模塊可以訪問到多個和操作系統聯系緊密的變量和函數。
??? ○ `fileinput`:通過該模塊可以輕松遍歷多個文件和流中所有的行。
??? ○ `sets`、`heapq`和`deque`:這3個模塊提供了3個有用的數據結構。集合也以內建的類型`set`存在。
??? ○ `time`:通過該模塊可以獲取當前時間,并可進行時間日期操作和格式化。
??? ○ `random`:通過該模塊中的函數可以產生隨機數,從序列中選取隨機元素以及打亂列表元素。
??? ○ `shelve`:通過該模塊可以創建持續性映射,同時將映射的內容保存在給定文件名的數據庫中。
??? ○ `re`:支持正則表達式的模塊。
如果想要了解更多模塊,再次建議你瀏覽[Python類庫參考](http://python.org/doc/lib),讀起來真的很有意思。
### 10.4.1 本章的新函數
本章涉及的新函數如表10-11所示。
表10-11 本章的新函數
```
dir(obj) 返回按字母順序排序的屬性名稱列表
help([obj]) 提供交互式幫助或關于特定對象的交互式幫助信息
reload(module) 返回已經導入模塊的重新載入版本,該函數在Python3.0將要被廢除
```
### 10.4.2 接下來學什么
如果讀者能夠掌握本章某些概念,那么你的Python編程水平就會有很大程度的提高。使用手頭上的標準庫可以讓Python從強大變得無比強大。以目前學到的知識為基礎,讀者已經能編寫出用于解決很多問題的程序了。下一章將會介紹如何使用Python和外部世界——文件以及網絡——進行交互,從而讓讀者能夠解決更多問題。