6. 模組¶
如果你退出 Python 直譯器再重新進入,你所做的定義(函式和變數)就會丟失。因此,如果你想編寫一個稍微長一點的程式,最好使用文字編輯器準備直譯器的輸入,然後使用該檔案作為輸入執行。這被稱為建立**指令碼**。隨著程式變長,你可能想將其拆分成多個檔案,以便於維護。你可能還想在多個程式中使用一個你編寫的便利函式,而無需將其定義複製到每個程式中。
為了支援這一點,Python 提供了一種將定義放入檔案並在指令碼或直譯器的互動式例項中使用它們的方法。這樣的檔案稱為**模組**;模組中的定義可以**匯入**到其他模組或**主**模組(你在頂級執行的指令碼和計算器模式下可以訪問的變數集合)中。
模組是一個包含 Python 定義和語句的檔案。檔名是模組名附加字尾 .py
。在一個模組中,模組名(作為字串)可以透過全域性變數 __name__
的值獲取。例如,使用你喜歡的文字編輯器在當前目錄中建立一個名為 fibo.py
的檔案,其內容如下:
# Fibonacci numbers module
def fib(n):
"""Write Fibonacci series up to n."""
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
print()
def fib2(n):
"""Return Fibonacci series up to n."""
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a+b
return result
現在進入 Python 直譯器並使用以下命令匯入此模組:
>>> import fibo
這不會將 fibo
中定義的函式名稱直接新增到當前的 名稱空間 中(有關更多詳細資訊,請參閱 Python 作用域和名稱空間);它只會在那裡新增模組名稱 fibo
。你可以使用模組名稱訪問這些函式:
>>> fibo.fib(1000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fibo.fib2(100)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>> fibo.__name__
'fibo'
如果你打算經常使用某個函式,可以將其賦值給一個區域性名稱:
>>> fib = fibo.fib
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
6.1. 關於模組的更多資訊¶
模組可以包含可執行語句和函式定義。這些語句旨在初始化模組。它們只在匯入語句中**第一次**遇到模組名時執行。[1](如果檔案作為指令碼執行,它們也會執行。)
每個模組都有自己的私有名稱空間,所有在模組中定義的函式都將其用作全域性名稱空間。因此,模組的作者可以在模組中使用全域性變數,而無需擔心與使用者的全域性變數意外衝突。另一方面,如果你知道自己在做什麼,你可以使用與引用其函式相同的表示法 modname.itemname
來操作模組的全域性變數。
模組可以匯入其他模組。習慣上但不是必須將所有 import
語句放在模組(或指令碼)的開頭。如果匯入的模組名位於模組的頂層(在任何函式或類之外),它們將被新增到模組的全域性名稱空間中。
有一個 import
語句的變體,它直接將模組中的名稱匯入到匯入模組的名稱空間中。例如:
>>> from fibo import fib, fib2
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
這不會將匯入來源的模組名稱引入區域性名稱空間(因此在示例中,fibo
未定義)。
甚至還有一種變體可以匯入模組定義的所有名稱:
>>> from fibo import *
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
這會匯入所有名稱,除了那些以下劃線 (_
) 開頭的名稱。在大多數情況下,Python 程式設計師不使用此功能,因為它會向直譯器引入一組未知的名稱,可能會隱藏你已經定義的一些內容。
請注意,通常情況下,從模組或包中匯入 *
是一種不推薦的做法,因為它通常會導致程式碼難以閱讀。但是,在互動式會話中使用它可以節省輸入。
如果模組名稱後跟 as
,則 as
後面的名稱直接繫結到匯入的模組。
>>> import fibo as fib
>>> fib.fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
這實際上與 import fibo
匯入模組的方式相同,唯一的區別是它現在可以透過 fib
訪問。
它也可以在使用 from
時使用,效果類似:
>>> from fibo import fib as fibonacci
>>> fibonacci(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
備註
出於效率考慮,每個模組在每個直譯器會話中只匯入一次。因此,如果更改了模組,則必須重新啟動直譯器——或者,如果只是想互動式地測試一個模組,請使用 importlib.reload()
,例如 import importlib; importlib.reload(modulename)
。
6.1.1. 將模組作為指令碼執行¶
當你使用以下命令執行 Python 模組時:
python fibo.py <arguments>
模組中的程式碼將被執行,就像你匯入它一樣,但 __name__
將設定為 "__main__"
。這意味著透過在模組末尾新增這段程式碼:
if __name__ == "__main__":
import sys
fib(int(sys.argv[1]))
你可以使該檔案既可用作指令碼,也可用作可匯入模組,因為解析命令列的程式碼只在模組作為“主”檔案執行時執行:
$ python fibo.py 50
0 1 1 2 3 5 8 13 21 34
如果模組被匯入,則程式碼不會執行:
>>> import fibo
>>>
這通常用於為模組提供方便的使用者介面,或用於測試目的(將模組作為指令碼執行會執行測試套件)。
6.1.2. 模組搜尋路徑¶
當匯入名為 spam
的模組時,直譯器首先搜尋具有該名稱的內建模組。這些模組名稱列在 sys.builtin_module_names
中。如果未找到,它將根據變數 sys.path
給出的目錄列表搜尋名為 spam.py
的檔案。sys.path
從這些位置初始化:
包含輸入指令碼的目錄(或未指定檔案時的當前目錄)。
PYTHONPATH
(目錄名稱列表,語法與 shell 變數PATH
相同)。安裝相關的預設值(根據約定包含一個
site-packages
目錄,由site
模組處理)。
更多詳細資訊請參閱 sys.path 模組搜尋路徑的初始化。
備註
在支援符號連結的檔案系統上,包含輸入指令碼的目錄是在跟蹤符號連結後計算的。換句話說,包含符號連結的目錄**不會**新增到模組搜尋路徑中。
初始化後,Python 程式可以修改 sys.path
。執行的指令碼所在的目錄被放置在搜尋路徑的開頭,位於標準庫路徑之前。這意味著該目錄中的指令碼將被載入,而不是庫目錄中同名的模組。除非有意替換,否則這是一個錯誤。有關更多資訊,請參閱 標準模組 一節。
6.1.3. “編譯”Python 檔案¶
為了加快模組載入速度,Python 將每個模組的編譯版本快取在 __pycache__
目錄中,名稱為 module.version.pyc
,其中版本編碼了編譯檔案的格式;它通常包含 Python 版本號。例如,在 CPython 3.3 版本中,spam.py 的編譯版本將被快取為 __pycache__/spam.cpython-33.pyc
。這種命名約定允許來自不同版本和不同 Python 版本的編譯模組共存。
Python 會檢查原始檔的修改日期與編譯版本進行比較,以檢視它是否過時需要重新編譯。這是一個完全自動化的過程。此外,編譯後的模組是平臺無關的,因此同一個庫可以在不同架構的系統之間共享。
Python 在兩種情況下不檢查快取。首先,它總是重新編譯並且不儲存直接從命令列載入的模組的結果。其次,如果沒有源模組,它不檢查快取。為了支援非源(僅編譯)分發,編譯模組必須位於源目錄中,並且不能存在源模組。
一些專家提示:
你可以使用 Python 命令上的
-O
或-OO
開關來減小編譯模組的大小。-O
開關移除斷言語句,-OO
開關同時移除斷言語句和 __doc__ 字串。由於某些程式可能依賴於這些可用性,你只應在你瞭解其作用時才使用此選項。“最佳化”模組帶有opt-
標籤,通常更小。未來的版本可能會改變最佳化的效果。程式從
.pyc
檔案讀取時並不會比從.py
檔案讀取時執行得更快;.pyc
檔案唯一快的地方是載入速度。模組
compileall
可以為目錄中的所有模組建立 .pyc 檔案。關於此過程的更多詳細資訊,包括決策流程圖,請參閱 PEP 3147。
6.2. 標準模組¶
Python 附帶了一個標準模組庫,在單獨的文件《Python 庫參考》(以下簡稱“庫參考”)中進行了描述。有些模組內建在直譯器中;它們提供對不屬於語言核心但仍內建的操作的訪問,無論是為了效率還是為了提供對作業系統原語(如系統呼叫)的訪問。這些模組的集合是一個配置選項,也取決於底層平臺。例如,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
,則由內建預設值初始化。你可以使用標準的列表操作對其進行修改:
>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')
6.3. dir()
函式¶
內建函式 dir()
用於查詢模組定義了哪些名稱。它返回一個排序的字串列表:
>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__',
'__interactivehook__', '__loader__', '__name__', '__package__', '__spec__',
'__stderr__', '__stdin__', '__stdout__', '__unraisablehook__',
'_clear_type_cache', '_current_frames', '_debugmallocstats', '_framework',
'_getframe', '_git', '_home', '_xoptions', 'abiflags', 'addaudithook',
'api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix',
'breakpointhook', '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', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth',
'getallocatedblocks', 'getdefaultencoding', 'getdlopenflags',
'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile',
'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval',
'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
'intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value',
'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks',
'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'pycache_prefix',
'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'setdlopenflags',
'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr',
'stdin', 'stdout', 'thread_info', 'unraisablehook', '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. 包¶
包是一種透過使用“點分模組名”來構造 Python 模組名稱空間的方式。例如,模組名 A.B
指示包 A
中的一個名為 B
的子模組。就像使用模組可以避免不同模組的作者擔心彼此的全域性變數名一樣,使用點分模組名可以避免 NumPy 或 Pillow 等多模組包的作者擔心彼此的模組名。
假設你想設計一個模組集合(一個“包”),用於統一處理聲音檔案和聲音資料。有許多不同的聲音檔案格式(通常透過其副檔名識別,例如:.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
中的目錄中搜索包子目錄。
__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 package import item
時,item
可以是包的子模組(或子包),也可以是包中定義的其他名稱,例如函式、類或變數。import
語句首先測試 item
是否在包中定義;如果沒有,它假定它是一個模組並嘗試載入它。如果找不到它,則會引發 ImportError
異常。
相反,當使用像 import item.subitem.subsubitem
這樣的語法時,除了最後一個之外,每個 item 都必須是一個包;最後一個 item 可以是一個模組或一個包,但不能是前一個 item 中定義的類、函式或變數。
6.4.1. 從包中匯入 *¶
那麼當用戶寫 from sound.effects import *
時會發生什麼呢?理想情況下,人們會希望它能以某種方式訪問檔案系統,找到包中存在的子模組,並全部匯入它們。但這可能需要很長時間,而且匯入子模組可能會產生不必要的副作用,這些副作用只應在子模組被顯式匯入時發生。
唯一的解決方案是包作者提供一個明確的包索引。當遇到 from package import *
時,import
語句使用以下約定:如果包的 __init__.py
程式碼定義了一個名為 __all__
的列表,則將其視為應匯入的模組名稱列表。包作者有責任在釋出新版本時保持此列表的最新狀態。如果包作者認為從其包中匯入 * 沒有用處,他們也可以決定不支援它。例如,檔案 sound/effects/__init__.py
可能包含以下程式碼:
__all__ = ["echo", "surround", "reverse"]
這意味著 from sound.effects import *
將匯入 sound.effects
包的三個命名子模組。
請注意,子模組可能會被區域性定義的名稱遮蔽。例如,如果你向 sound/effects/__init__.py
檔案添加了一個 reverse
函式,則 from sound.effects import *
將只匯入兩個子模組 echo
和 surround
,但**不**匯入 reverse
子模組,因為它被區域性定義的 reverse
函式遮蔽了:
__all__ = [
"echo", # refers to the 'echo.py' file
"surround", # refers to the 'surround.py' file
"reverse", # !!! refers to the 'reverse' function now !!!
]
def reverse(msg: str): # <-- this name shadows the 'reverse.py' submodule
return msg[::-1] # in the case of a 'from sound.effects import *'
如果未定義 __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 *
時只匯出遵循特定模式的名稱,但在生產程式碼中仍然被認為是糟糕的做法。
請記住,使用 from package import specific_submodule
是完全沒有問題的!事實上,這是推薦的寫法,除非匯入模組需要使用來自不同包的同名子模組。
6.4.2. 包內引用¶
當包被結構化為子包時(如示例中的 sound
包),你可以使用絕對匯入來引用兄弟包的子模組。例如,如果模組 sound.filters.vocoder
需要使用 sound.effects
包中的 echo
模組,它可以使用 from sound.effects import echo
。
你也可以編寫相對匯入,使用 from module import name
形式的匯入語句。這些匯入使用前導點來指示相對匯入中涉及的當前包和父包。例如,從 surround
模組中,你可以使用:
from . import echo
from .. import formats
from ..filters import equalizer
請注意,相對匯入是基於當前模組的包名稱。由於主模組沒有包,因此旨在用作 Python 應用程式主模組的模組必須始終使用絕對匯入。
6.4.3. 多目錄中的包¶
包支援另一個特殊的屬性,__path__
。在執行包的 __init__.py
檔案中的程式碼之前,它被初始化為一個字串 序列,其中包含持有該包的 __init__.py
檔案的目錄名。此變數可以修改;這樣做會影響將來對包中包含的模組和子包的搜尋。
雖然這個功能不常需要,但它可以用來擴充套件包中找到的模組集。
腳註