[譯]The Python Tutorial#Modules
6. Modules
如果你從Python解釋器中退出然后重新進入,之前定義的名字(函數(shù)和變量)都丟失了。因此,如果你想寫長一點的程序,使用文本編輯器來準備解釋器的輸入會更好,使用文件作為替代的輸入。這也被稱作創(chuàng)建腳本。當(dāng)程序越來越長時,出于易于維護的原因,你可能會將程序分割為幾個文件。你也可能想要在多個程序中使用很好用的一個函數(shù),而不用將其定義拷貝到每一個程序中。
為了支持這些需求,Python提供了將定義放入一個文件的方式,并且在腳本或者解釋器交互式實例中使用它們。這樣的文件稱為模塊;模塊中的定義可以導(dǎo)入到其他模塊或者主模塊中(在頂層執(zhí)行的腳本和計算模式中可訪問到的變量集合)。
模塊就是一個包含Python定義和語句的文件。文件名是模塊名并且?guī)в?code>.py后綴。在模塊中,模塊的名字(作為字符串),作為全局變量__name__
的值,是可用的。例如,使用你最喜歡的文本編輯器在當(dāng)前目錄創(chuàng)建fibo.py
文件,內(nèi)容如下:
# Fibonacci numbers module
def fib(n): # write Fibonacci series up to n
a, b = 0, 1
while b < n:
print(b, end=' ')
a, b = b, a+b
print()
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
進入Python解釋器,使用下列命令導(dǎo)入這個模塊:
>>> import fibo
這個操作并不會講fibo
中定義的函數(shù)的名字導(dǎo)入到當(dāng)前符號表中;只是導(dǎo)入模塊的名字fibo
。使用模塊名字可訪問到函數(shù):
>>> 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'
如果想要頻繁使用函數(shù),可以將其賦值給局部名字:
>>> fib = fibo.fib
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
6.1 More on Modules
模塊可以同時包含可執(zhí)行語句和函數(shù)定義??蓤?zhí)行語句用來初始化模塊。當(dāng)模塊名字出現(xiàn)在導(dǎo)入語句中時,這些可執(zhí)行語句只執(zhí)行一次[1]。(如果文件作為腳本,這些可執(zhí)行也會執(zhí)行)
每一個模塊都有自己私有的符號表,這個符號表被所有定義在模塊中的函數(shù)作為全局符號表使用。因此,模塊的作者可以使用這些全局變量,而不用擔(dān)心和用于全局變量偶然的名字沖突。另一方面,如果你知道自己在做什么,你可以使用與引用函數(shù)相同的方法來引用模塊的全局變量,modname.itemname
。
模塊可以引用其他模塊。將所有import
語句放置到模塊的開始處(或者腳本)是一個很好的習(xí)慣,但并不是強制的。被導(dǎo)入的模塊名字將會被放置到當(dāng)前模塊的全局符號表中。
有一種導(dǎo)入語句的變種方法,將模塊的名字直接導(dǎo)入到當(dāng)前模塊的符號表中。例如:
>>> from fibo import fib, fib2
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
以上并不會引入模塊的名字(在上面的例子中fibo
不會被定義)。
甚至有一種方法可以導(dǎo)入模塊中定義的所有名字:
>>> from fibo import *
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
這種方法導(dǎo)入所有不以下劃線_
開頭的名字。大多數(shù)情況下,Python程序員不會使用這種方法,因為這會導(dǎo)入未知的名字集合到解釋器中,也許還會屏蔽已經(jīng)定義的一些名字。
需要注意,通常在實踐中從模塊或者包中導(dǎo)入所以名字是不鼓勵使用的,因為會降低程序的易讀性。然而,在交互式環(huán)境中使用它來減少打字輸入是可行的。
注意: 出于性能原因,一個解釋器會話中每個模塊只導(dǎo)入一次。因此,如果模塊被改變了,必須重啟解釋器;如果只想要交互式測試一個模塊,使用
importlib.reload()
,例如:import importlib; importlin.reload(modulename)
6.1.1 Executing modules as scripts
當(dāng)使用以下命令運行Python模塊:
python fibo.py <arguments>
模塊中的代碼會被執(zhí)行,就像導(dǎo)入該模塊一樣,但是這時__name__
被設(shè)置為__main__
。這意味著以下代碼會加入到模塊末尾:
if __name__ == "__main__":
import sys
fib(int(sys.argv[1]))
模塊即作為腳本執(zhí)行,也可以作為模塊導(dǎo)入,因為只有當(dāng)模塊作為mian
文件執(zhí)行時候,解析命令行的代碼才會執(zhí)行:
$ python fibo.py 50
1 1 2 3 5 8 13 21 34
如果模塊被導(dǎo)入,代碼不會執(zhí)行:
>>> import fibo
>>>
這可以用來為使用者提供一個模塊用戶接口的使用約定,也可以用作測試(模塊作為腳本時執(zhí)行測試用例)
6.1.2 The Module Search Path
當(dāng)模塊spam
被導(dǎo)入時,解釋器首先搜索built-in
模塊。如果沒有找到,解釋器在變量sys.path
提供的路徑列表中搜索名為spam.py
的文件。sys.path
從下列位置初始化:
- 包含輸入腳本的目錄(或者沒有指定文件時的當(dāng)前目錄)
-
PYTHONPATH
(目錄名字集合,與shell環(huán)境變量PATH
相似,也是環(huán)境變量) - 安裝默認目錄
注意: 在支持符號鏈接的文件系統(tǒng),包含輸入腳本的目錄是符號鏈接指向的目錄。也就是說包含符號鏈接的目錄不會被加入到搜索路徑中。
初始化后,Python程序可以修改sys.path
。包含執(zhí)行腳本的目錄被放置到搜索路徑的開始,在標準庫路徑之前。這意味著該目錄中的腳本會被加載,而標準庫目錄中的同名模塊不會被加載。這會引發(fā)錯誤,除非是有意替換標準庫的模塊。閱讀Standard Modules獲取更多信息。 (譯注:自定義的模塊不應(yīng)與標準模塊重名,否則標準模塊會被覆蓋。)
6.1.3 “Compiled” Python files
為加速模塊加載,Python會在__pycache__
目錄中緩存每個模塊的編譯版本,緩存文件名為module.version.pyc
,version
編碼了被編譯文件的版本;通常包含了Python的版本號。例如,在CPython release 3.3
中,文件spam.py
的編譯版本會被緩存為__pycache__/spam.cpython-33.pyc
。這種命名約定允許來自不同Python發(fā)行版本的模塊得以共存。
Python檢查源文件的修改日期與編譯版本,來確定編譯版本是否過期,是否需要重新編譯。這是一個完全自動化的過程。另外,編譯模塊是平臺獨立的,因此異構(gòu)系統(tǒng)可以共享相同的庫。
Python不會檢查在兩個環(huán)境中的緩存。首先,Python總是重新編譯,并且不會存儲直接從命令行加載的模塊的結(jié)果。其次,如果沒有源模塊,Python不檢查緩存。若要支持無源文件(只有編譯版本)分布,那么編譯的模塊必須放在源文件目錄中,并且源模塊必需不存在。
給專家的建議:
- 可以在命令行使用
-O
或者-OO
開關(guān)來減少編譯模塊的大小。-O
參數(shù)移除assert
語句,-OO
參數(shù)同時移除assert
語句和__doc__
字符串。由于一些程序依賴這些變量,那么只有當(dāng)你確認你要這么做時,才能使用這兩個參數(shù)?!皟?yōu)化的”模塊有一個opt-
標簽并且通常更小。未來的發(fā)型版本可能改變優(yōu)化的影響。 - 從
.pyc
文件中讀取的程序不會比從.py
文件讀取的程序跑得快;.pyc
文件快的地方在于加載。 -
compileall
模塊可以為目錄中的所有模塊創(chuàng)建.pyc
文件。 - PEP 3147中有關(guān)系這點的更多信息,包括一個決策流程
6.2 Standard Modules
Python提供了標準模塊庫,在獨立文檔中描述,名為Python Library Reference
(以后叫做Library Reference
)。有一些模塊內(nèi)嵌入解釋器中,這些模塊不是語言核心的一部分,但是它們是內(nèi)嵌的,這既是為性能考慮,也提供了訪問如系統(tǒng)調(diào)用般的操作系統(tǒng)原生接口的方式。這些模塊集合依賴底層平臺的配置選項。例如winreg
模塊只在Windows系統(tǒng)中提供。一個特殊的模塊值得注意:sys
,這個模塊內(nèi)嵌在所有Python解釋器中。變量sys.ps1
和sys.ps2
定義了主提示符和輔助提示符的字符串:
>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print('Yuck!')
Yuck!
C>
只有當(dāng)解釋器以交互模式運行才會定義這兩個變量。
變量sys.path
是決定解釋器模塊搜索路徑的字符列表。該變量從環(huán)境變量PYTHONPATH
,或者內(nèi)置默認路徑(PYTHONPATH
未指定時)初始化??梢允褂脴藴?code>list操作修改它的值:
>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')
6.3 The dir() Function
內(nèi)嵌函數(shù)dir()
用于搜索模塊定義的名字。返回一個有序字符串列表:
>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)
['__displayhook__', '__doc__', '__excepthook__', '__loader__', '__name__',
'__package__', '__stderr__', '__stdin__', '__stdout__',
'_clear_type_cache', '_current_frames', '_debugmallocstats', '_getframe',
'_home', '_mercurial', '_xoptions', 'abiflags', 'api_version', 'argv',
'base_exec_prefix', 'base_prefix', 'builtin_module_names', 'byteorder',
'call_tracing', 'callstats', 'copyright', 'displayhook',
'dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix',
'executable', 'exit', 'flags', 'float_info', 'float_repr_style',
'getcheckinterval', 'getdefaultencoding', 'getdlopenflags',
'getfilesystemencoding', 'getobjects', 'getprofile', 'getrecursionlimit',
'getrefcount', 'getsizeof', 'getswitchinterval', 'gettotalrefcount',
'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
'intern', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path',
'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1',
'setcheckinterval', 'setdlopenflags', 'setprofile', 'setrecursionlimit',
'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout',
'thread_info', 'version', 'version_info', 'warnoptions']
不帶參數(shù)使用dir()
函數(shù),會列出當(dāng)前作用域的全部名字:
>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']
需要注意該函數(shù)列出所有類型的名字:變量,模塊,函數(shù),等等。
dir()
不會列出內(nèi)嵌函數(shù)和變量的名字。如果希望列出,這些名字定義在標準模塊builtins
中:
>>> import builtins
>>> dir(builtins)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException',
'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning',
'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError',
'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning',
'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False',
'FileExistsError', 'FileNotFoundError', 'FloatingPointError',
'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError',
'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError',
'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError',
'MemoryError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented',
'NotImplementedError', 'OSError', 'OverflowError',
'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError',
'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning',
'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError',
'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError',
'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning',
'ValueError', 'Warning', 'ZeroDivisionError', '_', '__build_class__',
'__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs',
'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable',
'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits',
'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit',
'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr',
'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass',
'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview',
'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property',
'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice',
'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars',
'zip']
6.4 Packages
包是使用“圓點模塊名”結(jié)構(gòu)化Python模塊名字空間的方式。例如,模塊名A.B
表示包A
中的子模塊B
。就像模塊使得不同模塊的作者免于擔(dān)憂每個模塊的全局名字一樣,圓點模塊名的使用使得多模塊包(如NumPy
或者Python圖像庫)的作者免于擔(dān)憂每個模塊的名字。
假設(shè)需要為統(tǒng)一音頻文件和音頻數(shù)據(jù)的處理設(shè)計一個模塊的集合(包)。有許多不同的音頻文件格式(通常通過擴展名辨認,如.wav, .aiff, .au
),因此需要為不同文件格式的轉(zhuǎn)換創(chuàng)建和維護一個持續(xù)增長的模塊集合。也存在許多對音頻數(shù)據(jù)不同的操作(例如混合,增加回聲,增加均衡器函數(shù),創(chuàng)建人造立體效果),因此需要額外編寫執(zhí)行這些操作的大量模塊。以下是包的可能結(jié)構(gòu)(以層級文件結(jié)構(gòu)來表示):
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.py
reverse.py
...
filters/ Subpackage for filters
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
導(dǎo)入包時,Python搜索sys.path
提供的路徑尋找包子目錄。
為使Python將普通目錄看做包,目錄中必須包含__init__.py
文件。這樣做是為了避免普通的目錄名(如string
)將以后會出現(xiàn)在模塊搜索路徑中的有效模塊無意識的隱藏掉。最簡單的情況是,__init__.py
可以是一個空文件,但是它也可以包含可執(zhí)行的初始化包的代碼或者設(shè)置__all__
變量,后面講述。
包的用戶可以從包中導(dǎo)入獨立的模塊:
import sound.effects.echo
這將加在子模塊sound.effects.echo
。必須使用全名引用:
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
導(dǎo)入子模塊可選方式:
from sound.effects import echo
以上也加載子模塊echo
,不使用包前綴引用模塊,因此可以像下面一樣使用:
echo.echofilter(input, output, delay=0.7, atten=4)
另外的方式是直接導(dǎo)入需要的函數(shù)或者變量:
from sound.effects.echo import echofilter
同樣的,這將加在子模塊echo,但使函數(shù)echofilter()
直接可用:
echofilter(input, output, delay=0.7, atten=4)
注意當(dāng)使用from package import item
時,item
可以使子模塊(子包),也可以是包內(nèi)定義的其他名字,如函數(shù),類或者變量。import
語句首先測試要導(dǎo)入的項是否在包中存在;如果不存在,Python假設(shè)這個項是模塊并嘗試加載它。如果最后尋找失敗,ImportError
異常拋出。
相對的,使用import item.subitem.subsubitem
時,除了最后一項,每一項都必須是包;最后一項可以是模塊或者包但是不能是前面的項中定義的類,函數(shù)或者變量。
6.4.1 Importing * From a Package
使用from sound.effects import *
會發(fā)生什么?理想情況下,總是期望在文件系統(tǒng)中找出所有子模塊,并全部導(dǎo)入。全部導(dǎo)入會耗費很長時間,并且導(dǎo)入子模塊可能會有不期待的副作用,這些副作用應(yīng)該在顯式導(dǎo)入時發(fā)生。
唯一的解決方案是包作者提供一個包的顯式索引。import
語句遵循以下約定:如果包的__init__.py
代碼中定義了名為__all__
的變量,那么使用from package import *
時會導(dǎo)入該變量指定的所有模塊。當(dāng)包的新版本發(fā)布時,由包作者負責(zé)更新列表__all__
。如果包作者不希望可以使用from package import *
導(dǎo)入包中的模塊,也可以不支持__all__
。 例如,文件sound/effects/__init__.py
可能包含以下代碼:
__all__ = ["echo", "surround", "reverse"]
這意味著from sound.effects import *
會導(dǎo)入sound
包中指定的三個模塊。
如果__all__
沒有定義,語句from sound.effects import *
不會將包sound.effects
中的子模塊全部倒入到當(dāng)前名字空間中,只保證包sound.effects
被導(dǎo)入了(可能會運行__init__.py
中的初始化代碼)并且導(dǎo)入任意在包中定義的名字。包括在__init__.py
中定義的任意名字(以及顯式加載的子模塊)。也會包括前面的import
語句顯式加載的任意包子模塊。考慮如下代碼:
import sound.effects.echo
import sound.effects.surround
from sound.effects import *
這個例子中,echo
和surround
模塊被導(dǎo)入到當(dāng)前名字空間中,因為執(zhí)行from... import
時,它們已經(jīng)定義在sound.effects
包中定義了。(定義了__all__
時同樣有效)
盡管使用import *
時只有符合特定模式的名字會被導(dǎo)出,但仍然不建議在生產(chǎn)代碼中使用。
記住,使用form Package import specific_submodle
沒有錯誤。實際上,這是推薦的方法,除非當(dāng)前模塊需要使用其他包中的同名模塊。
6.4.2 Intra-package References
當(dāng)包中包含了子包結(jié)構(gòu)(就如例子中的sound
包),可以使用絕對導(dǎo)入的方式引用兄弟包中的子模塊。例如,如果模塊sound.filters.vocoder
需要使用包sound.effects
中的echo
模塊,可以使用from sound.effects import echo
。
也可以使用from modul import name
語句來相對導(dǎo)入模塊。這種方式使用點.
指示當(dāng)前包和相對導(dǎo)入中涉及的父包。以surround
模塊為例:
from . import echo
from .. import formats
from ..filters import equalizer
相對導(dǎo)入基于當(dāng)前模塊的名字。由于主模塊的名字總是__main__
,要當(dāng)做Python應(yīng)用主模塊使用的模塊必須總是使用絕對導(dǎo)入的方式。
6.4.3 Packages in Multiple Directories
包還支持一個特殊屬性,__path__
。在__init__.py
中的代碼執(zhí)行之前,屬性__path__
被初始化為一個列表,這個列表包含了持有該__init__.py
文件的目錄的路徑。該變量可修改,這樣做會影響將來的對包內(nèi)模塊和子包的搜索。
然而這個特性不總是需要的,它可以用來擴展包的模塊集合
Footnotes
<span id="jump">[1] </span>實際上,函數(shù)定義也是可“執(zhí)行”的“語句”;模塊級別的函數(shù)執(zhí)行將函數(shù)的名字放置到模塊的全局符號表中