第六章 模塊
last edited 2 months ago by panjy
如果退出Python解釋程序然後再進入,原有的定義(函數和變量)就丟失了。所以,如果 需要寫長一點的程序,最好用一個文本編輯程序為解釋程序准備輸入,然後以程序文件作為 輸入來運行Python解釋程序,這稱為准備腳本(script)。當你的程序變長時,最好把它拆 分成幾個文件以利於維護。你還可能想在幾個程序中都使用某個很方便的函數,但又不想把 函數定義賦值到每一個程序中。
為了支持這些,Python有一種辦法可以把定義放在一個文件中然後就可以在一個腳本中或 交互運行中調用。這樣的文件叫做一個模塊;模塊中的定義可以導入其它模塊或主模塊(主 模塊指在解釋程序頂級執行的腳本或交互執行的程序所能訪問的變量集合)。
模塊是包含了Python定義和語句的文件。文件名由模塊名加上後綴“.py”構成。在模塊 內,模塊的名字(作為一個字符串)可以由全局變量__name__的值獲知。例如,在Python的 搜索路徑中用你習慣使用的文本編輯器(Python 1.5.2包含了一個用Tkinter編寫的IDLE集成 開發環境,MS Windows下有一個PythonWin?界面也可以進行Python程序編輯)生成一個名為“fibo.py ”的文件,包含如下內容:
# Fibonacci numbers module
def fib(n): # 輸出小於n的Fibonacci序列
a, b = 0, 1
while b < n:
print b,
a, b = b, a+b
def fib2(n): # 返回小於n的Fibonacci序列
result = []
a, b = 0, 1
while b < n:
result.append(b)
a, b = b, a+b
return result
然後進入Python解釋程序(在IDLE或PythonWin?中可以直接進入解釋程序窗口),用如下 命令可以導入模塊:
>>> import fibo
這不會把模塊fibo中的函數的名字直接引入當前的符號表,這只是把模塊名fibo引入。可 以用模塊名來訪問其中的函數:
>>> fibo.fib(1000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fibo.fib2(100)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>> fibo.__name__
'fibo'
如果經常使用某個函數可以給它賦一個局部名字:
>>> fib = fibo.fib
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
6.1 模塊的進一步介紹
模塊除了可以包含函數定義之外也可以包含可執行語句。這些可執行語句用來初始化模塊 ,它們只在模塊第一次被導入時執行。
每個模塊有自己私有的符號表,這個私有符號表對於模塊中的所有函數而言卻是它們的全 局符號表。因此,模塊作者可以在模塊中使用全局變量而不需擔心與模塊用戶的全局變量沖 突。另一方面,如果你有把握的話也可以用訪問模塊中函數的格式,即modname.itemname的 方法來修改模塊中的全局變量。
模塊可以導入其它模塊。我們通常把所有的導入語句放在模塊(或腳本)的開始位置,這 不是規定要求的。導入的模塊名放入模塊的全局符號表中。
導入還有另一種用法,可以把模塊中的名字直接導入使用者的符號表。例如:
>>> from fibo import fib, fib2
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
這不會把模塊名導入使用者的符號表中(例如,上面例子中fibo就沒有定義)。
還有一種辦法可以導入一個模塊中定義的所有名字:
>>> from fibo import *
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
這可以把模塊中除了以下劃線結尾的所有名字導入。
6.1.1 模塊搜索路徑
在導入名為spam的模塊時,解釋程序先在當前目錄中尋找名為“spam.py”的文件,然後 從環境變量PYTHONPATH所定義的目錄列表中尋找。PYTHONPATH的用法和可執行文件的搜索路 徑PATH用法相同,都是一個目錄列表。當PYTHONPATH未設置的時候,或者文件仍找不到,則 搜索繼續在安裝時設定的缺省路徑搜索,在Unix中,這通常是“.:/usr/local/lib/python” 。
實際上,模塊是按變量sys.path指定的路徑搜索的,此變量在解釋程序啟動時初始化為包 含輸入腳本的目錄(或當前路徑),PYTHONPATH和安裝缺省路徑。這樣,用戶可以通過修改sys.path 來修改和替換模塊搜索路徑。參見後面關於標准模塊的一節。
6.1.2 “編譯”的Python文件
為了提高調用許多標准模塊的小程序的啟動時間,一個重要的措施是,如果在找到“spam.py ”的目錄中存在一個名為“spam.pyc”的文件,就認為此文件包含了模塊spam的一個所謂“ 字節編譯”版本。用於生成“spam.pyc”的“spam.py”的修改時間被記入了“spam.pyc”中 ,如果記錄的修改時間與現在文件的時間不相符的話就忽略編譯文件。
一般不需要自己生成“spam.pyc”這樣的編譯文件。每當“spam.py”成功編譯後解釋程 序就嘗試寫編譯版本“spam.pyc”,如果不可寫也不會出錯;如果因為某種原因此文件沒有 寫完則生成的“spam.pyc”被識別為不完整的而被忽略。編譯文件“spam.pyc”的格式是不 依賴於平台的,所以不同結構的機器可以共享Python模塊目錄。
下面是對專家的一些竅門:
如果Python解釋程序是以-O標志啟動的,將生成優化的編譯代碼,保存在“.pyo”文件 中。目前優化不是很多,現在只是去掉assert語句和SET_LINENO指令。使用了-O標志時,所 有字節碼都是優化的,“.pyc”文件被忽略,“.py”文件被編譯為優化的字節碼。
給Python解釋程序兩個優化標志(-OO)產生的優化代碼有時會導致程序運行不正常。 目前雙重優化只從字節碼中刪除了__doc__字符串,使得“.pyo”文件較小。有些程序可能是 依賴於文檔字符串的,所以只有在確知不會有問題時才可以使用這樣的優化。
從“.pyc”或“.pyo”讀入的程序並不能比從“.py”讀入的運行更快,它們只是調入 速度更快一些。
如果一個程序是用在命令行指定腳本文件名的方式運行的,腳本的字節碼不會寫入“.pyc ”或“.pyo”文件。所以如果把程序的主要代碼都移入一個模塊,腳本中只剩下導入該模塊 的引導程序則可以略微縮短腳本的啟動時間。
可以有叫做“spam.pyc”(當用了-O標志時為“spam.pyo”)的文件而沒有對應的源文 件“spam.py”。這可以用來分發一個比較難反編譯的Python代碼庫。
模塊compileall可以把一個目錄中所有模塊編譯為“.pyc”文件(指定了-O選項時編譯 為“.pyo”文件)。
6.2 標准模塊
Python帶有一個標准模塊庫,在另一個文檔《Python庫參考》中進行了描述。一些模塊直 接編入了解釋程序中,這些模塊不是語言的核心,為了運行效率或者為了提供對於系統調用 這樣的系統底層功能而編入了解釋程序中。提供那些模塊是編譯時的選擇,例如,amoeba模 塊只在提供amoeba底層指令的系統中才能提供。
有一個模塊值得特別重視:sys模塊,每一個Python解釋程序中都編譯入了這個模塊。變 量sys.ps1和sys.ps2定義了交互運行時的初始提示和續行提示。
>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print 'YUCk!'
Yuck!
C>
這兩個變量只在解釋程序以交互方式運行時才有定義。
變量sys.path是一個字符串列表,由它確定解釋程序的模塊搜索路徑。它被初始化為環境 變量PYTHONPATH所指定的缺省路徑,環境變量沒有定義時初始化為安裝時的缺省路徑。可以 用標准的列表操作修改這個搜索路徑,例如:
>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')
6.3 dir()函數
內置函數dir()用於列出一個模塊所定義的名字,它返回一個字符串列表:
>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)
['__name__', 'argv', 'builtin_module_names', 'copyright', 'exit',
'maxint', 'modules', 'path', 'ps1', 'ps2', 'setprofile', 'settrace',
'stderr', 'stdin', 'stdout', 'version']
沒有自變量時,dir()列出當前定義的名字。
>>> a = [1, 2, 3, 4, 5]
>>> import fibo, sys
>>> fib = fibo.fib
>>> dir()
['__name__', 'a', 'fib', 'fibo', 'sys']
注意dir()列出了所有各類名字:變量名、模塊名、函數名,等等。dir()不會列出內置函 數、變量的名字。要想列出內置名字的話需要使用標准模塊__builtin__:
>>> import __builtin__
>>> dir(__builtin__)
['AccessError', 'AttributeError', 'ConflictError', 'EOFError', 'IOError',
'ImportError', 'IndexError', 'KeyError', 'KeyboardInterrupt',
'MemoryError', 'NameError', 'None', 'OverflowError', 'RuntimeError',
'SyntaxError', 'SystemError', 'SystemExit', 'TypeError', 'ValueError',
'ZeroDivisionError', '__name__', 'abs', 'apply', 'chr', 'cmp', 'coerce',
'compile', 'dir', 'divmod', 'eval', 'execfile', 'filter', 'float',
'getattr', 'hasattr', 'hash', 'hex', 'id', 'input', 'int', 'len', 'long',
'map', 'max', 'min', 'oct', 'open', 'ord', 'pow', 'range', 'raw_input',
'reduce', 'reload', 'repr', 'round', 'setattr', 'str', 'type', 'xrange']
6.4 包
Python中可以用“包”來組織Python的模塊名字空間,名字引用時可以用“帶點的模塊名 。例如,模塊名A.B代表包“A”內名為“B”的子模塊。正如使用模塊可以使不同模塊的作者 不用顧慮彼此的全局變量名會沖突,使用帶點的模塊名可以使多模塊包如NumPy?和PIL的作者 不需要擔心彼此的模塊名會沖突。
假設你有一系列處理聲音文件和聲音數據的模塊(稱為一個“包”)。有許多種不同的聲 音文件格式(通常用擴展名來識別,如“wav”,“.aiff”,“.au”),所以你可能需要制 作並維護一組不斷增加的模塊來處理不同文件格式的轉換。你還可能需要對聲音數據進行許 多不同的操作(如混音、回響、均衡、產生模擬立體聲效果),所以你還需要不斷增加模塊 來執行這些操作。一下是你的程序包的可能的結構(用一個分層文件系統表示):
Sound/ 頂層包
__init__.py 初始化音響包
Formats/ 用於文件格式轉換的子程序包
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
Effects/ 用於音響效果的子程序包
__init__.py
echo.py
surround.py
reverse.py
...
Filters/ 用於濾波的子程序包
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
包目錄中的“__init__.py”文件是必須得,用來指示Python把這個目錄看成包,這可以 防止有相同名字如“string”的子目錄掩蓋住在搜索路徑後面一些出現的模塊定義。在最簡 單的情況下,“__init__.py”可以是一個空文件,它也可以包含初始化包所需的代碼,和設 置“__all__”變量,這些後面會加以討論。
包的用戶可以從包中導入單獨的模塊,如:
import Sound.Effects.echo
這可以把子模塊Sound.Effects.echo導入。要引用它也必須用全名,例如:
Sound.Effects.echo.echofilter(input, output, delay=0.7, atten=4)
導入子模塊的另一種辦法是:
from Sound.Effects import echo
這同樣也導入子模塊echo,但調用時不需寫包前綴,所以可以用如:
echo.echofilter(input, output, delay=0.7, atten=4)
另外一種寫法是直接導入所需的函數或變量:
from Sound.Effects.echo import echofilter
這一次同樣是調入了子模塊echo,但是使其函數echofilter直接可用:
echofilter(input, output, delay=0.7, atten=4)
注意使用“from 包 import 項”這樣的格式時,導入的項可以是包的一個子模塊(或子 包),也可以是包內定義的其它名字如函數、類、變量。導入語句首先查找包內是否定義了 所需的項,如果沒有則假設它是一個模塊然後調入。如果找不到,結果引起ImportError?。
相反的,當使用“import item.subitem.subsubitem”這樣的格式時,除最後一個外其它 各項都應該是包,最後一項可以是包也可以是模塊,不允許是前面一項內部定義的類、函數 或變量。
6.4.1 從包中導入*
現在,如果用戶寫“from Sound.Effects import *”會發生什麼情況?理想情況下我們 希望這應該掃描文件系統,找到所有包內的子模塊並把它們都導入進來。不幸的是這種操作 在Mac和Windows平台上不能准確實現,這兩種操作系統對文件名的大小寫沒有准確信息。在 這些平台上,不知道名為“ECHO.PY”的文件會作為模塊echo、Echo還是ECHO被導入。(例如 ,Windows 95在顯示文件名時總是討厭地把第一個字母大寫)。DOS的8+3文件名限制更是對 長模塊名造成了有趣的困難。
這個問題的唯一解決辦法是由模塊作者顯式地提供包的索引。引入*的import語句遵循如 下規定:如果包的“__init__.py”文件定義了一個名為“__all__”的列表,這個列表就作 為從包內導入*時要導入的所有模塊的名字表。因此當包的新版本發布時需要包的作者確保這 個列表是最新的。包的作者如果認為不需要導入*的話也可以不支持這種用法。例如, 文件Sounds/Effects/__init__.py 可以包含如下代碼:
__all__ = ["echo", "surround", "reverse"]
這意味著from Sound.Effects import *將從Sound包中導入指定的三個子包。
如果沒有定義__all__,則from Sound.Effects import *語句不會導入Sound.Effects包 中的所有子模塊;此語句只能保證Sound.Effects被導入(可能是執行其初始化代碼“__init__.py ”)並導入包中直接定義的名字。這包括由“__init__.py”定義的任何名字和顯式導入的子 模塊名。這也包括模塊中已經在前面用import顯式地導入的子模塊,例如:
import Sound.Effects.echo
import Sound.Effects.surround
from Sound.Effects import *
在這個例子中,echo和surround模塊被導入當前名字空間,因為它們在執行from...import 語句時已定義(在定義了__all__的情況下這一點也是成立的)。
注意用戶應盡量避免使用從模塊或包中導入*的做法,因為這樣經常導致可讀性差的代碼 。盡管如此,在交互運行時可以用導入*的辦法節省敲鍵次數,而且有些模塊在設計時就考慮 到了這個問題,它們只輸出遵循某種約定的名字。注意,from 包 import 特定子模塊的用法 並沒有錯,實際上這還是我們推薦的用法,除非程序還需要用到來自其它包的同名的子模塊 。
6.4.2 包內部引用
子模塊常常需要彼此引用。例如,模塊surround可能要用到模塊echo。事實上,這樣的引 用十分常見,所以import語句首先從子模塊的所在包中尋找要導入的子模塊才在標准模塊搜 索路徑查找。所以,模塊surround只要寫import echo或from echo import echofilter。如 果在包含本模塊的包中沒有找到要導入的模塊,import語句將去尋找指定名字的頂級模塊。
當包組織成子包時(比如例中的Sound包),沒有一種簡單的辦法可以引用兄弟包中的子 模塊――必須使用子模塊的全名。例如,如果模塊Sound.Filters.vocoder要引用Sound.Effects 包中的echo模塊,它可以用Sound.Effects import echo。
這個問題的唯一解決辦法是由模塊作者顯式地提供包的索引。引入*的import語句遵循如 下規定:如果包的“__init__.py”文件定義了一個名為“__all__”的列表,這個列表就作 為從包內導入*時要導入的所有模塊的名字表。因此當包的新版本發布時需要包的作者確保這 個列表是最新的。包的作者如果認為不需要導入*的話也可以不支持這種用法。例如, 文件Sounds/Effects/__init__.py 可以包含如下代碼:
__all__ = ["echo", "surround", "reverse"]
這意味著from Sound.Effects import *將從Sound包中導入指定的三個子包。
如果沒有定義__all__,則from Sound.Effects import *語句不會導入Sound.Effects包 中的所有子模塊;此語句只能保證Sound.Effects被導入(可能是執行其初始化代碼“__init__.py ”)並導入包中直接定義的名字。這包括由“__init__.py”定義的任何名字和顯式導入的子 模塊名。這也包括模塊中已經在前面用import顯式地導入的子模塊,例如:
import Sound.Effects.echo
import Sound.Effects.surround
from Sound.Effects import *
在這個例子中,echo和surround模塊被導入當前名字空間,因為它們在執行from...import 語句時已定義(在定義了__all__的情況下這一點也是成立的)。
注意用戶應盡量避免使用從模塊或包中導入*的做法,因為這樣經常導致可讀性差的代碼 。盡管如此,在交互運行時可以用導入*的辦法節省敲鍵次數,而且有些模塊在設計時就考慮 到了這個問題,它們只輸出遵循某種約定的名字。注意,from 包 import 特定子模塊的用法 並沒有錯,實際上這還是我們推薦的用法,除非程序還需要用到來自其它包的同名的子模塊 。
6.4.2 包內部引用
子模塊常常需要彼此引用。例如,模塊surround可能要用到模塊echo。事實上,這樣的引 用十分常見,所以import語句首先從子模塊的所在包中尋找要導入的子模塊才在標准模塊搜 索路徑查找。所以,模塊surround只要寫import echo或from echo import echofilter。如 果在包含本模塊的包中沒有找到要導入的模塊,import語句將去尋找指定名字的頂級模塊。
當包組織成子包時(比如例中的Sound包),沒有一種簡單的辦法可以引用兄弟包中的子 模塊――必須使用子模塊的全名。例如,如果模塊Sound.Filters.vocoder要引用Sound.Effects 包中的echo模塊,它可以用Sound.Effects import echo。
__all__ = ["echo", "surround", "reverse"]
這意味著from Sound.Effects import *將從Sound包中導入指定的三個子包。
如果沒有定義__all__,則from Sound.Effects import *語句不會導入Sound.Effects包 中的所有子模塊;此語句只能保證Sound.Effects被導入(可能是執行其初始化代碼“__init__.py ”)並導入包中直接定義的名字。這包括由“__init__.py”定義的任何名字和顯式導入的子 模塊名。這也包括模塊中已經在前面用import顯式地導入的子模塊,例如:
import Sound.Effects.echo
import Sound.Effects.surround
from Sound.Effects import *
在這個例子中,echo和surround模塊被導入當前名字空間,因為它們在執行from...import 語句時已定義(在定義了__all__的情況下這一點也是成立的)。
注意用戶應盡量避免使用從模塊或包中導入*的做法,因為這樣經常導致可讀性差的代碼 。盡管如此,在交互運行時可以用導入*的辦法節省敲鍵次數,而且有些模塊在設計時就考慮 到了這個問題,它們只輸出遵循某種約定的名字。注意,from 包 import 特定子模塊的用法 並沒有錯,實際上這還是我們推薦的用法,除非程序還需要用到來自其它包的同名的子模塊 。
6.4.2 包內部引用
子模塊常常需要彼此引用。例如,模塊surround可能要用到模塊echo。事實上,這樣的引 用十分常見,所以import語句首先從子模塊的所在包中尋找要導入的子模塊才在標准模塊搜 索路徑查找。所以,模塊surround只要寫import echo或from echo import echofilter。如 果在包含本模塊的包中沒有找到要導入的模塊,import語句將去尋找指定名字的頂級模塊。
當包組織成子包時(比如例中的Sound包),沒有一種簡單的辦法可以引用兄弟包中的子 模塊――必須使用子模塊的全名。例如,如果模塊Sound.Filters.vocoder要引用Sound.Effects 包中的echo模塊,它可以用Sound.Effects import echo。