The Python Tutorial » 6. Modules 翻譯 ...
[譯]The Python Tutorial#Modules
6. Modules
如果你從Python解釋器中退出然後重新進入,之前定義的名字(函數和變數)都丟失了。因此,如果你想寫長一點的程式,使用文本編輯器來準備解釋器的輸入會更好,使用文件作為替代的輸入。這也被稱作創建腳本。當程式越來越長時,出於易於維護的原因,你可能會將程式分割為幾個文件。你也可能想要在多個程式中使用很好用的一個函數,而不用將其定義拷貝到每一個程式中。
為了支持這些需求,Python提供了將定義放入一個文件的方式,並且在腳本或者解釋器互動式實例中使用它們。這樣的文件稱為模塊;模塊中的定義可以導入到其他模塊或者主模塊中(在頂層執行的腳本和計算模式中可訪問到的變數集合)。
模塊就是一個包含Python定義和語句的文件。文件名是模塊名並且帶有.py
尾碼。在模塊中,模塊的名字(作為字元串),作為全局變數__name__
的值,是可用的。例如,使用你最喜歡的文本編輯器在當前目錄創建fibo.py
文件,內容如下:
# 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解釋器,使用下列命令導入這個模塊:
>>> 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 More on Modules
模塊可以同時包含可執行語句和函數定義。可執行語句用來初始化模塊。當模塊名字出現在導入語句中時,這些可執行語句只執行一次[1]。(如果文件作為腳本,這些可執行也會執行)
每一個模塊都有自己私有的符號表,這個符號表被所有定義在模塊中的函數作為全局符號表使用。因此,模塊的作者可以使用這些全局變數,而不用擔心和用於全局變數偶然的名字衝突。另一方面,如果你知道自己在做什麼,你可以使用與引用函數相同的方法來引用模塊的全局變數,modname.itemname
。
模塊可以引用其他模塊。將所有import
語句放置到模塊的開始處(或者腳本)是一個很好的習慣,但並不是強制的。被導入的模塊名字將會被放置到當前模塊的全局符號表中。
有一種導入語句的變種方法,將模塊的名字直接導入到當前模塊的符號表中。例如:
>>> 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
這種方法導入所有不以下劃線_
開頭的名字。大多數情況下,Python程式員不會使用這種方法,因為這會導入未知的名字集合到解釋器中,也許還會屏蔽已經定義的一些名字。
需要註意,通常在實踐中從模塊或者包中導入所以名字是不鼓勵使用的,因為會降低程式的易讀性。然而,在互動式環境中使用它來減少打字輸入是可行的。
註意: 出於性能原因,一個解釋器會話中每個模塊只導入一次。因此,如果模塊被改變了,必須重啟解釋器;如果只想要互動式測試一個模塊,使用
importlib.reload()
,例如:import importlib; importlin.reload(modulename)
6.1.1 Executing modules as scripts
當使用以下命令運行Python模塊:
python fibo.py <arguments>
模塊中的代碼會被執行,就像導入該模塊一樣,但是這時__name__
被設置為__main__
。這意味著以下代碼會加入到模塊末尾:
if __name__ == "__main__":
import sys
fib(int(sys.argv[1]))
模塊即作為腳本執行,也可以作為模塊導入,因為只有當模塊作為mian
文件執行時候,解析命令行的代碼才會執行:
$ python fibo.py 50
1 1 2 3 5 8 13 21 34
如果模塊被導入,代碼不會執行:
>>> import fibo
>>>
這可以用來為使用者提供一個模塊用戶介面的使用約定,也可以用作測試(模塊作為腳本時執行測試用例)
6.1.2 The Module Search Path
當模塊spam
被導入時,解釋器首先搜索built-in
模塊。如果沒有找到,解釋器在變數sys.path
提供的路徑列表中搜索名為spam.py
的文件。sys.path
從下列位置初始化:
- 包含輸入腳本的目錄(或者沒有指定文件時的當前目錄)
PYTHONPATH
(目錄名字集合,與shell環境變數PATH
相似,也是環境變數)- 安裝預設目錄
註意: 在支持符號鏈接的文件系統,包含輸入腳本的目錄是符號鏈接指向的目錄。也就是說包含符號鏈接的目錄不會被加入到搜索路徑中。
初始化後,Python程式可以修改sys.path
。包含執行腳本的目錄被放置到搜索路徑的開始,在標準庫路徑之前。這意味著該目錄中的腳本會被載入,而標準庫目錄中的同名模塊不會被載入。這會引發錯誤,除非是有意替換標準庫的模塊。閱讀Standard Modules獲取更多信息。 (譯註:自定義的模塊不應與標準模塊重名,否則標準模塊會被覆蓋。)
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發行版本的模塊得以共存。
Python檢查源文件的修改日期與編譯版本,來確定編譯版本是否過期,是否需要重新編譯。這是一個完全自動化的過程。另外,編譯模塊是平臺獨立的,因此異構系統可以共用相同的庫。
Python不會檢查在兩個環境中的緩存。首先,Python總是重新編譯,並且不會存儲直接從命令行載入的模塊的結果。其次,如果沒有源模塊,Python不檢查緩存。若要支持無源文件(只有編譯版本)分佈,那麼編譯的模塊必須放在源文件目錄中,並且源模塊必需不存在。
給專家的建議:
- 可以在命令行使用
-O
或者-OO
開關來減少編譯模塊的大小。-O
參數移除assert
語句,-OO
參數同時移除assert
語句和__doc__
字元串。由於一些程式依賴這些變數,那麼只有當你確認你要這麼做時,才能使用這兩個參數。“優化的”模塊有一個opt-
標簽並且通常更小。未來的髮型版本可能改變優化的影響。 - 從
.pyc
文件中讀取的程式不會比從.py
文件讀取的程式跑得快;.pyc
文件快的地方在於載入。 compileall
模塊可以為目錄中的所有模塊創建.pyc
文件。- PEP 3147中有關係這點的更多信息,包括一個決策流程
6.2 Standard Modules
Python提供了標準模塊庫,在獨立文檔中描述,名為Python Library Reference
(以後叫做Library Reference
)。有一些模塊內嵌入解釋器中,這些模塊不是語言核心的一部分,但是它們是內嵌的,這既是為性能考慮,也提供了訪問如系統調用般的操作系統原生介面的方式。這些模塊集合依賴底層平臺的配置選項。例如winreg
模塊只在Windows系統中提供。一個特殊的模塊值得註意:sys
,這個模塊內嵌在所有Python解釋器中。變數sys.ps1
和sys.ps2
定義了主提示符和輔助提示符的字元串:
>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print('Yuck!')
Yuck!
C>
只有當解釋器以交互模式運行才會定義這兩個變數。
變數sys.path
是決定解釋器模塊搜索路徑的字元列表。該變數從環境變數PYTHONPATH
,或者內置預設路徑(PYTHONPATH
未指定時)初始化。可以使用標準list
操作修改它的值:
>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')
6.3 The dir() Function
內嵌函數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']
不帶參數使用dir()
函數,會列出當前作用域的全部名字:
>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']
需要註意該函數列出所有類型的名字:變數,模塊,函數,等等。
dir()
不會列出內嵌函數和變數的名字。如果希望列出,這些名字定義在標準模塊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
包是使用“圓點模塊名”結構化Python模塊名字空間的方式。例如,模塊名A.B
表示包A
中的子模塊B
。就像模塊使得不同模塊的作者免於擔憂每個模塊的全局名字一樣,圓點模塊名的使用使得多模塊包(如NumPy
或者Python圖像庫)的作者免於擔憂每個模塊的名字。
假設需要為統一音頻文件和音頻數據的處理設計一個模塊的集合(包)。有許多不同的音頻文件格式(通常通過擴展名辨認,如.wav, .aiff, .au
),因此需要為不同文件格式的轉換創建和維護一個持續增長的模塊集合。也存在許多對音頻數據不同的操作(例如混合,增加回聲,增加均衡器函數,創建人造立體效果),因此需要額外編寫執行這些操作的大量模塊。以下是包的可能結構(以層級文件結構來表示):
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
...
導入包時,Python搜索sys.path
提供的路徑尋找包子目錄。
為使Python將普通目錄看做包,目錄中必須包含__init__.py
文件。這樣做是為了避免普通的目錄名(如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 package import item
時,item
可以使子模塊(子包),也可以是包內定義的其他名字,如函數,類或者變數。import
語句首先測試要導入的項是否在包中存在;如果不存在,Python假設這個項是模塊並嘗試載入它。如果最後尋找失敗,ImportError
異常拋出。
相對的,使用import item.subitem.subsubitem
時,除了最後一項,每一項都必須是包;最後一項可以是模塊或者包但是不能是前面的項中定義的類,函數或者變數。
6.4.1 Importing * From a Package
使用from sound.effects import *
會發生什麼?理想情況下,總是期望在文件系統中找出所有子模塊,並全部導入。全部導入會耗費很長時間,並且導入子模塊可能會有不期待的副作用,這些副作用應該在顯式導入時發生。
唯一的解決方案是包作者提供一個包的顯式索引。import
語句遵循以下約定:如果包的__init__.py
代碼中定義了名為__all__
的變數,那麼使用from package import *
時會導入該變數指定的所有模塊。當包的新版本發佈時,由包作者負責更新列表__all__
。如果包作者不希望可以使用from package import *
導入包中的模塊,也可以不支持__all__
。 例如,文件sound/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
時,它們已經定義在sound.effects
包中定義了。(定義了__all__
時同樣有效)
儘管使用import *
時只有符合特定模式的名字會被導出,但仍然不建議在生產代碼中使用。
記住,使用form Package import specific_submodle
沒有錯誤。實際上,這是推薦的方法,除非當前模塊需要使用其他包中的同名模塊。
6.4.2 Intra-package References
當包中包含了子包結構(就如例子中的sound
包),可以使用絕對導入的方式引用兄弟包中的子模塊。例如,如果模塊sound.filters.vocoder
需要使用包sound.effects
中的echo
模塊,可以使用from sound.effects import echo
。
也可以使用from modul import name
語句來相對導入模塊。這種方式使用點.
指示當前包和相對導入中涉及的父包。以surround
模塊為例:
from . import echo
from .. import formats
from ..filters import equalizer
相對導入基於當前模塊的名字。由於主模塊的名字總是__main__
,要當做Python應用主模塊使用的模塊必須總是使用絕對導入的方式。
6.4.3 Packages in Multiple Directories
包還支持一個特殊屬性,__path__
。在__init__.py
中的代碼執行之前,屬性__path__
被初始化為一個列表,這個列表包含了持有該__init__.py
文件的目錄的路徑。該變數可修改,這樣做會影響將來的對包內模塊和子包的搜索。
然而這個特性不總是需要的,它可以用來擴展包的模塊集合
Footnotes
[1] 實際上,函數定義也是可“執行”的“語句”;模塊級別的函數執行將函數的名字放置到模塊的全局符號表中