5. 模塊 Modules
想要將程序長(zhǎng)久保存,方便維護(hù)。Python 提供了一個(gè)方法可以從文件中獲取定義,在腳本或者解釋器的一個(gè)交互式實(shí)例中使用。在模塊中的定義可以導(dǎo)入到另一個(gè)模塊或主模塊中。
模塊是包括Python定義和聲明的文件。文件名就是模塊名加上“.py”后綴。模塊的模塊名可以由全局變量__name__得到。
# Fibonacci numbers module
def fib(n): # write Fibonacci series up to n
a, b = 0, 1
while b < n:
print b
a, b = b, a+b
def fib2(n): # return Fibonacci series up to n
result = []
a, b = 0, 1
while b < n:
result.append(b)
a, b = b, a+b
return result
保存為fibo.py到本地文件夾。
運(yùn)行cmd,cd到fibo.py所在文件夾,python命令進(jìn)入解釋器,使用下面命令導(dǎo)入這個(gè)模塊
這樣做不會(huì)直接把fibo中的函數(shù)導(dǎo)入當(dāng)前的語(yǔ)義表;它只是引入了模塊名fibo。通過(guò)使用模塊名字,你可以訪(fǎng)問(wèn)模塊中的函數(shù):
>>> fibo.fib(1000)
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
>>> fibo.__name__
'fibo'
>>>
如果你經(jīng)常使用一個(gè)函數(shù),你可以可以給它賦一個(gè)本地名稱(chēng):
>>> fib = fibo.fib
>>> fib(10)
1
1
2
3
5
8
>>>
5.1 深入模塊
模塊可以像函數(shù)定義一樣包含執(zhí)行語(yǔ)句。這些語(yǔ)句通暢用于初始化模塊。它們只在模塊第一次導(dǎo)入時(shí)執(zhí)行一次。對(duì)應(yīng)于在模塊中定義的所有函數(shù)的全局語(yǔ)義表,每一個(gè)模塊都有自己的私有語(yǔ)義表。因此,模塊作者可以在模塊中使用一些全局變量,而不用擔(dān)心與用戶(hù)的全局變量意外沖突而引發(fā)錯(cuò)誤。另一方面,如果你確定需要,可以像引用模塊中的函數(shù)一樣獲取模塊中的全局變量,形式modname.itemname。
模塊中可以導(dǎo)入其他模塊。習(xí)慣上所有的import語(yǔ)句都放在模塊(或腳本,等等)的開(kāi)頭,但這并不是必須的。被導(dǎo)入的模塊名被放在模塊的全局語(yǔ)義表中。
import語(yǔ)句的一個(gè)變體直接從被導(dǎo)入的模塊中導(dǎo)入命名到本模塊的語(yǔ)義表中。
>>> from fibo import fib, fib2
>>>
這樣不會(huì)在本地語(yǔ)義表中導(dǎo)入模塊名。(上面的例子,fibo沒(méi)有定義)
甚至可以導(dǎo)入模塊中的所有定義:
>>> from fibo import *
>>>
這樣可以導(dǎo)入所有除了以下劃線(xiàn)(_)開(kāi)頭的命名。
5.1.1 模塊搜索路徑
如果要導(dǎo)入的模塊名字為spam,搜索順序?yàn)椋?/span>
1.在當(dāng)前目錄中搜索“spam.py”文件。
2.在環(huán)境變量PYTHONPATH代表的目錄列表中搜索。
3.搜索環(huán)境PATH中的路徑列表。
如果PYTHONPATH沒(méi)有設(shè)置,或者文件沒(méi)有找到,接下來(lái)搜索安裝目錄,在UNIX中,通常是“../usr/local/lib/python”。
實(shí)際上,解釋器由sys.path變量指定的路徑目錄搜索模塊,該變量初始化時(shí)默認(rèn)包含了輸入腳本(或當(dāng)前目錄),PYTHONPATH和安裝目錄。這樣就允許Python程序了解如何修改或替換模塊搜索目錄。需要注意的是由于這些目錄中吧哦哈uyou搜索路徑中運(yùn)行的腳本,所以這些腳本不應(yīng)該和標(biāo)準(zhǔn)模塊重名,否則Python會(huì)嘗試把加載的腳本作為模塊,當(dāng)導(dǎo)入模塊時(shí)。這通常會(huì)引發(fā)一個(gè)錯(cuò)誤。
5.1.2 編譯Python文件
對(duì)于引用了大量標(biāo)準(zhǔn)模塊的短程,有一個(gè)提高啟動(dòng)速度的重要方法,如果在“spam.py”所在的目錄下存在一個(gè)名為“spam.pyc”文件,它會(huì)被視為spam模塊的預(yù)“編譯”版本。用于創(chuàng)建“spam.pyc”的這一版“spam.py”的修改時(shí)間記錄在“spam.pyc”文件中,如果兩者不匹配,“.pyc”文件被忽略。
通常你不需要為創(chuàng)建“spam.pyc”文件作任何工作。一旦“spam.py”成功編譯,就會(huì)試圖編譯對(duì)應(yīng)版本的“spam.pyc”。如果有任何原因?qū)е聦?xiě)入不成功,返回的“spam.pyc”文件就會(huì)視為無(wú)效,隨后即被忽略。“spam.pyc”文件的內(nèi)容是平臺(tái)獨(dú)立的,所以python模塊目錄可以在不同架構(gòu)的機(jī)器之間共享。
部分高級(jí)技巧:
1.以-O參數(shù)調(diào)用Python解釋器時(shí),會(huì)生成優(yōu)化代碼并保存在“.pyo”文件中。目前的優(yōu)化器沒(méi)有太多幫助;它只是刪除了斷言語(yǔ)句。使用-O參數(shù)時(shí),所有的字節(jié)碼都會(huì)被優(yōu)化;.pyc文件被忽略,.py文件被編譯為優(yōu)化的字節(jié)碼。
2.向Python解釋器傳遞兩個(gè)-O參數(shù)會(huì)執(zhí)行完全優(yōu)化的二進(jìn)制優(yōu)化編譯,這偶爾會(huì)生成錯(cuò)誤的程序。目前的優(yōu)化器,只是從二進(jìn)制代碼中刪除了__doc__字符串,生成更為緊湊的“.pyo”文件。因?yàn)橐恍┏绦蛞蕾?lài)這些變量的可用性,你應(yīng)該只在確定無(wú)誤的場(chǎng)合使用這一選項(xiàng)。
3.來(lái)自“.pyc”或“.pyo”文件中的程序不會(huì)比來(lái)自“.py”文件的運(yùn)行更快;“.pyc”或“.pyo”文件只是在它們加載的時(shí)候更快一些。
4.通過(guò)腳本名在命令行運(yùn)行腳本時(shí),不會(huì)為該腳本創(chuàng)建的字節(jié)碼代碼寫(xiě)入“.pyc”或“.pyo”文件。如此,用一個(gè)小的啟動(dòng)腳本導(dǎo)入這個(gè)模塊,就可以提高腳本的啟動(dòng)速度。也可以直接在命令行中指定一個(gè)“.pyc”或“.pyo”文件。
5.對(duì)于同一個(gè)模塊(如spam.py),可以只有“spam.pyc”文件(或者“spam.pyo”)而沒(méi)有“spam.py”文件。這樣可以打包發(fā)布比較難于逆向工程的Python代碼庫(kù)。
6.模塊可以為指定目錄中的所有模塊創(chuàng)建“.pyc”文件(或者是“.pyo”文件)。
5.2 標(biāo)準(zhǔn)模塊
Python帶有一個(gè)標(biāo)準(zhǔn)模塊庫(kù),并發(fā)布由獨(dú)立的文檔,名為Python庫(kù)參考手冊(cè)。有一些模塊內(nèi)置于解釋器中,這些操作的訪(fǎng)問(wèn)接口不是語(yǔ)言?xún)?nèi)核的一部分,但是已經(jīng)內(nèi)置于解釋器了。這既是為了提高效率,也是為了提供操作系統(tǒng)原生接口,如系統(tǒng)調(diào)用。這類(lèi)模塊集合是一個(gè)依賴(lài)于底層平臺(tái)的配置選項(xiàng)。例如,amoeba模塊只提供對(duì)Amoeba原生系統(tǒng)的支持。有一個(gè)具體的模塊值得注意:sys,這個(gè)模塊內(nèi)置于所有的Python解釋器。變量sys.ps1和sys.ps2定義了主提示符和副提示符字符串:
>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>>
這兩個(gè)變量只在解釋器的交互模式下有意義。
變量sys.path是解釋器模塊搜索路徑的字符串列表。它由環(huán)境變量PYTHONPATH初始化,如果沒(méi)有設(shè)定PYTHONPATH,就由內(nèi)置的默認(rèn)值初始化。可以用標(biāo)準(zhǔn)的字符串操作修改它:
5.3 dir()函數(shù)
內(nèi)置函數(shù)dir()用于按模塊名搜索模塊定義,它返回一個(gè)字符串類(lèi)型的存儲(chǔ)列表:
>>> import fibo, sys
>>> dir(fibo)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'fib', 'fib2']
>>> dir(sys)
['__displayhook__', '__doc__', '__excepthook__', '__name__', '__package__', '__stderr__', '__stdin__', '__stdout__', '_clear_type_cache', '_current_frames', '_getframe', 'api_version', 'appargv', 'appargvoffset', 'argv', 'builtin_module_names', 'byteorder', 'call_tracing', 'callstats', 'copyright', 'displayhook', 'dllhandle', 'dont_write_bytecode', 'exc_clear', 'exc_info', 'exc_type', 'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info', 'getcheckinterval', 'getdefaultencoding', 'getfilesystemencoding', 'getprofile', 'getrecursionlimit', 'getrefcount', 'getsizeof', 'gettrace', 'getwindowsversion', 'hexversion', 'maxint', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'py3kwarning', 'setcheckinterval', 'setprofile', 'setrecursionlimit', 'settrace', 'stderr', 'stdin', 'stdout', 'subversion', 'version', 'version_info', 'warnoptions', 'winver']
>>>
無(wú)參數(shù)調(diào)用時(shí),dir()函數(shù)返回當(dāng)前定義的命名:
>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'a', 'fib', 'fibo', 'pywin', 'sys']
>>>
注意該列表列出了所有類(lèi)型的名稱(chēng):變量,模塊,函數(shù),等等:
dir()不會(huì)列出內(nèi)置函數(shù)和變量名。如果想列出這些內(nèi)容,它們?cè)跇?biāo)準(zhǔn)模塊__builtin__中定義:
>>> import __builtin__
>>> dir(__builtin__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs', 'all', 'any', 'apply', 'basestring', 'bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'long', 'map', 'max', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']
>>>
5.4 包
包通常是使用用“圓點(diǎn)模塊名”的結(jié)構(gòu)化模塊命名空間。例如,名為A.B的模塊表示,名為“A”的包中名為“B”的子模塊。正如同用模塊來(lái)保存不同的模塊架構(gòu)可以避免全局變量之間的相互沖突,使用圓點(diǎn)模塊名保存箱NumPy或Python Imaging Library之類(lèi)的不同類(lèi)庫(kù)架構(gòu)可以避免模塊之間的命名沖突。
假設(shè)你現(xiàn)在想要設(shè)計(jì)一個(gè)模塊集(一個(gè)“包”)來(lái)統(tǒng)一處理聲音文件和聲音數(shù)據(jù)。存在幾種不同的聲音格式,于是,為了在不同類(lèi)型的文件格式之間轉(zhuǎn)換,需要維護(hù)一個(gè)不斷增長(zhǎng)的包集合。可能你還想要對(duì)聲音數(shù)據(jù)做很多不同的操作,所以你要加入一個(gè)無(wú)線(xiàn)留模塊來(lái)執(zhí)行這些操作。你的包可能會(huì)是這個(gè)樣子(通過(guò)分級(jí)的文集體系來(lái)進(jìn)行分組)。
Sound/ Top-level package
__init__.py Initialize the sound package
Formats/ Subpackage for file format conversions
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
…
Effects/ Subpackage for sound effects
__init__.py
echo.py
surround.ppy
reverse.py
…
Filters/ Subpackage for filters
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
導(dǎo)入模塊時(shí),Python通過(guò)sys.path中的目錄列表來(lái)搜索存放包的子目錄。
必須要有一個(gè)“__init__.py”文件的存在,才能使Python視該目錄為一個(gè)包;這是為了防止某些目錄使用了“string”這樣的通用名而無(wú)意中在隨后的模塊搜索路徑中覆蓋了正確的模塊。最簡(jiǎn)單的情況下,“__init__.py”可以只是一個(gè)空文件,不過(guò)它也可能包含了包的初始化代碼,或者設(shè)置了__all__變量,后面會(huì)有相關(guān)介紹。
包用戶(hù)可以從包中導(dǎo)入合法的模塊,例如:
import Sound.Effects.echo
這樣就導(dǎo)入了Sound.Effects.echo子模塊。它必須通過(guò)完整的名稱(chēng)來(lái)引用。
Sound.Effects.echo.echofilter(input, output, delay=0.7, atten=4)
導(dǎo)入包時(shí)有一個(gè)可以選擇的方式:
from Sound.Effects import echo
這樣就加載了echo子模塊,并且使得它在沒(méi)有包前綴的情況下也可以使用,所以它可以如下方式調(diào)用:
echo.echofilter(input, output, delay=0.7, atten=4)
還有另一種變體用于直接導(dǎo)入函數(shù)或變量
from Sound.Effects.echo import echofilter
這樣就又一次加在了echo子模塊,但這樣就可以直接調(diào)用它的echofilter()函數(shù):
echofilter(input, output, delay=0.7, atten=4)
需要注意的是使用from package import item方式導(dǎo)入包時(shí),這個(gè)子項(xiàng)既可以是包中的衣柜子模塊,也可以是包中定義的其他命名,像函數(shù)、類(lèi)或變量。import語(yǔ)句首先核對(duì)是否包中有這個(gè)子項(xiàng),如果沒(méi)有,它假定這是一個(gè)模塊,并嘗試加載它。如果沒(méi)有找到它,會(huì)引發(fā)一個(gè)ImportError異常。
相反,使用類(lèi)似import item.subitem.subsubitem這樣的語(yǔ)法時(shí),這些子項(xiàng)必須是包,最后的子項(xiàng)可以是包或模塊,但不能是前面子項(xiàng)中定義的類(lèi)、函數(shù)或變量。
5.4.1 import * from a package
那么當(dāng)用戶(hù)寫(xiě)下from Sound.Effects import * 時(shí)會(huì)發(fā)生什么事?理想中,總是希望再文件系統(tǒng)中找出包中所有的子模塊,然后導(dǎo)入它們。不幸的是,這個(gè)操作在Mac和Windows平臺(tái)上工作的并不太好,這些文件系統(tǒng)的文件大小寫(xiě)并不敏感!在這些平臺(tái)上沒(méi)有什么方法可以確保一個(gè)叫“ECHO.PY”的文件應(yīng)該導(dǎo)入為模塊echo、Echo或ECHO。
對(duì)于包的作者來(lái)說(shuō)唯一的解決方案就是給提供一個(gè)明確的包索引。import語(yǔ)句按如下條件進(jìn)行轉(zhuǎn)換:執(zhí)行form package import *時(shí),如果包中的“__init__.py”代碼定義了一個(gè)名為__all__的鏈表,就會(huì)按照鏈表中給出的模塊名進(jìn)行導(dǎo)入。新版本包發(fā)布時(shí)作者可以任意更新這個(gè)鏈表。如果包作者不想import*的時(shí)候?qū)胨麄兊陌兴心K,那么也可能會(huì)決定不支持它(import*)例如,“Sounds/Effects/__init__.py”這個(gè)文件可能包括如下代碼:
__all__ = ["echo", "surround", "reverse"]
這意味著from Sound.Effects import * 語(yǔ)句會(huì)從Sound包中導(dǎo)入以上三個(gè)已命名的子模塊。
如果沒(méi)有定義__all__,from Sound.Effects import *語(yǔ)句不會(huì)從Sound.Effects包中導(dǎo)入所有的子模塊到當(dāng)前命名空間。Effects導(dǎo)入到當(dāng)前的命名空間,只能確定的是導(dǎo)入了Sound.Effects包(可能會(huì)運(yùn)行“__init__.py”中的初始化代碼)以及在包中定義的所有命名。這些包含任何通過(guò)“__init__.py”定義的命名(和明確導(dǎo)入的子模塊)。同業(yè)也包括了前述的import語(yǔ)句從包中明確導(dǎo)入的子模塊。看下面的代碼:
import sound.effects.echo
import sound.effects.surround
from sound.effects import *
在這個(gè)例子中,echo和surround模塊導(dǎo)入了當(dāng)前的命名空間,這是因?yàn)閳?zhí)行from…import語(yǔ)句時(shí)他們已經(jīng)被定義在Sound.Effects包中(定義了__all__時(shí)也會(huì)同樣工作)。
需要注意的是習(xí)慣上不主張從一個(gè)包貨模塊中用import*導(dǎo)入所有模塊,因?yàn)檫@樣通常意味著可讀性會(huì)很差。然而,在交互會(huì)話(huà)中,這做可以減少輸入,certain modules are designed to export only names that follow certain patterns。
記住,from package import specific_submodule沒(méi)喲錯(cuò)誤!事實(shí)上,除非導(dǎo)入的模塊需要使用其他包中的同名子模塊,否則這是推薦寫(xiě)法。
5.4.2 內(nèi)部包
子模塊之間需要互相引用。例如,surround模塊可能會(huì)引用echo模塊。事實(shí)上,這樣的引用如此普遍,以至于import語(yǔ)句會(huì)先搜索包內(nèi)部,然后才是標(biāo)準(zhǔn)模塊搜索路徑。因此surround模塊可以簡(jiǎn)單的調(diào)用import echo或者from echo import echofilter。如果沒(méi)有在當(dāng)前的包中發(fā)現(xiàn)要導(dǎo)入的模塊,import語(yǔ)句會(huì)依據(jù)指定名尋找一個(gè)頂級(jí)模塊。
如果包使用了子包結(jié)構(gòu)(就像示例中的Sound包),不存在什么從鄰近的包中引用子模塊的便捷方式--必須使用子包的全名。例如,如果Sound.Filters.vocoder包需要使用Sound.Effects包中的echosa模塊,它可以使用from Sound.Effects import echo。
(下面的是2.6中的內(nèi)容)
自Python2.5,加入了上述的隱含相對(duì)包含,你可以使用form module import name形式的引入語(yǔ)句來(lái)寫(xiě)顯示的相對(duì)包含。這些顯示的相對(duì)包含使用點(diǎn)號(hào)來(lái)指出當(dāng)前和父包參與到相對(duì)包含中。以surround模塊為例,你可以使用
from . import echo
from .. import formats
from ..filters import equalizer
注意顯示和隱式包含基于當(dāng)前模塊的名字。因此主模塊的名字通常是“__main__”,Python程序中的主模塊應(yīng)當(dāng)總是使用絕對(duì)路徑包含。
5.4.3 多重路徑中的包
包支持一個(gè)更為特殊的變量,__path__。在包的“__init__.py”文件代碼執(zhí)行之前,該變量初始化一個(gè)目錄名列表。該變量可以修改,它作用域包中的子包和模塊的搜索功能。
這個(gè)功能可以用于擴(kuò)展包中的模塊集,不過(guò)它不常用。