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 模組處理的 site-packages 目錄)。

更多詳細資訊請參見 sys.path 模組搜尋路徑的初始化

備註

在支援符號連結的檔案系統上,輸入指令碼所在的目錄是在符號連結被跟蹤後計算的。換句話說,包含符號連結的目錄不會新增到模組搜尋路徑中。

初始化後,Python 程式可以修改 sys.path。正在執行的指令碼所在的目錄放置在搜尋路徑的開頭,位於標準庫路徑之前。這意味著將載入該目錄中的指令碼,而不是庫目錄中同名的模組。除非有意進行替換,否則這是一個錯誤。有關更多資訊,請參見 標準模組 部分。

6.1.3. “已編譯”的 Python 檔案

為了加快模組的載入速度,Python 會將每個模組的編譯版本快取到 __pycache__ 目錄下,並命名為 module.version.pyc,其中 version 編碼了編譯檔案的格式;它通常包含 Python 的版本號。例如,在 CPython 3.3 版本中,spam.py 的編譯版本會快取為 __pycache__/spam.cpython-33.pyc。這種命名約定允許來自不同發行版和不同 Python 版本的編譯模組共存。

Python 會檢查原始碼的修改日期與編譯版本,以確定它是否已過期並需要重新編譯。這是一個完全自動的過程。此外,編譯後的模組是平臺獨立的,因此同一個庫可以在具有不同架構的系統之間共享。

在兩種情況下,Python 不會檢查快取。首先,它總是重新編譯,並且不會儲存從命令列直接載入的模組的結果。其次,如果沒有原始碼模組,它不會檢查快取。為了支援非原始碼(僅編譯)發行版,編譯後的模組必須位於源目錄中,並且不能有原始碼模組。

給專家的提示

  • 你可以在 Python 命令中使用 -O-OO 開關來減小編譯模組的大小。-O 開關刪除 assert 語句,-OO 開關刪除 assert 語句和 __doc__ 字串。由於某些程式可能依賴於這些資訊,因此只有當你清楚自己在做什麼時才應該使用此選項。“最佳化”的模組具有 opt- 標籤,並且通常較小。未來的版本可能會更改最佳化的效果。

  • .pyc 檔案讀取程式時,程式的執行速度並不比從 .py 檔案讀取程式時快;.pyc 檔案唯一的優點是載入速度更快。

  • 模組 compileall 可以為目錄中的所有模組建立 .pyc 檔案。

  • 關於此過程的更多詳細資訊,包括決策流程圖,請參閱 PEP 3147

6.2. 標準模組

Python 附帶一個標準模組庫,該庫在單獨的文件 Python 庫參考(以下簡稱“庫參考”)中進行了描述。一些模組內置於直譯器中;這些模組提供對不屬於語言核心,但仍然內建的操作的訪問,或者是為了提高效率,或者是為了提供對作業系統原語(如系統呼叫)的訪問。此類模組的集合是一個配置選項,它也取決於底層平臺。例如,winreg 模組僅在 Windows 系統上提供。有一個特定的模組值得關注:sys,它內置於每個 Python 直譯器中。變數 sys.ps1sys.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 指定一個名為 B 的子模組,該子模組位於名為 A 的包中。正如使用模組可以避免不同模組的作者擔心彼此的全域性變數名稱一樣,使用帶點的模組名稱可以避免 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 * 時會發生什麼?理想情況下,人們希望這會以某種方式進入檔案系統,找到包中存在的子模組,並將它們全部匯入。這可能需要很長時間,並且匯入子模組可能會產生不希望的副作用,這些副作用只應在顯式匯入子模組時發生。

唯一的解決方案是包作者提供包的顯式索引。 import 語句使用以下約定:如果包的 __init__.py 程式碼定義了一個名為 __all__ 的列表,則當遇到 from package import * 時,該列表將被視為應該匯入的模組名稱列表。 包作者有責任在釋出新版本的包時保持此列表的最新狀態。 如果他們認為從他們的包匯入 * 沒有用處,包作者也可以決定不支援它。 例如,檔案 sound/effects/__init__.py 可能包含以下程式碼

__all__ = ["echo", "surround", "reverse"]

這將意味著 from sound.effects import * 將匯入 sound.effects 包的三個命名的子模組。

請注意,子模組可能會被本地定義的名稱遮蔽。 例如,如果你在 sound/effects/__init__.py 檔案中新增一個 reverse 函式,則 from sound.effects import * 將只匯入兩個子模組 echosurround,而不會匯入 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 *

在此示例中,當執行 from...import 語句時,echosurround 模組會在當前名稱空間中匯入,因為它們是在 sound.effects 包中定義的。(當定義了 __all__ 時,這也有效。)

儘管某些模組被設計為在使用 import * 時僅匯出符合特定模式的名稱,但在生產程式碼中仍然被認為是糟糕的做法。

請記住,使用 from package import specific_submodule 沒有什麼問題! 實際上,除非匯入的模組需要使用來自不同包的同名子模組,否則這是推薦的表示法。

6.4.2. 包內引用

當包被結構化為子包時(如示例中的 sound 包),你可以使用絕對匯入來引用兄弟包的子模組。例如,如果模組 sound.filters.vocoder 需要使用 sound.effects 包中的 echo 模組,則可以使用 from sound.effects import echo

你也可以使用 import 語句的 from module import name 形式編寫相對匯入。 這些匯入使用前導點來指示相對匯入中涉及的當前包和父包。 例如,從 surround 模組中,你可能會使用

from . import echo
from .. import formats
from ..filters import equalizer

請注意,相對匯入是基於當前模組的名稱。 由於主模組的名稱始終為 "__main__",因此旨在用作 Python 應用程式主模組的模組必須始終使用絕對匯入。

6.4.3. 多個目錄中的包

包支援一個特殊的屬性,__path__。 在執行該檔案中的程式碼之前,它被初始化為包含儲存包的 __init__.py 的目錄名稱的字串的序列。 可以修改此變數;這樣做會影響將來對包中包含的模組和子包的搜尋。

雖然此功能並不常用,但可以用來擴充套件在包中找到的模組集。

腳註