6. 模組 (Module)¶
如果從 Python 直譯器離開後又再次進入,之前(幫函式或變數)做的定義都會消失。因此,想要寫一些比較長的程式時,你最好使用編輯器來準備要輸入給直譯器的內容,並且用該檔案來運行它。這就是一個腳本 (script)。隨著你的程式越變越長,你可能會想要把它分開成幾個檔案,讓它比較好維護。你可能也會想用一個你之前已經在其他程式寫好的函式,但不想要複製該函式的原始定義到所有使用它的程式裡。
為了支援這一點,Python 有一種方法可以將定義放入檔案中,並在互動模式下的直譯器中使用它們。這種檔案稱為模組 (module);模組中的定義可以被 import 到其他模組中,或是被 import 至主 (main) 模組(在最頂層執行的腳本,以及互動模式下,所使用的變數集合)。
模組是指包含 Python 定義和語句的檔案,檔案名稱是模組名稱加上 .py
。在模組中,模組的名稱(作為字串)會是全域變數 __name__
的值。例如,用你喜歡的文字編輯器在資料夾中創一個名為 fibo.py
的檔案,內容如下:
# 費波那契數模組
def fib(n):
"""寫出費波那契數列至第 n 位。"""
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
print()
def fib2(n):
"""回傳費波那契數列至第 n 位。"""
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a+b
return result
現在進入 Python 直譯器並用以下指令 import 這個模組:
>>> import fibo
這並不會將 fibo
中定義的函式名稱直接加入目前的 namespace 中(詳情請見 Python 作用域 (Scope) 及命名空間 (Namespace));它只會加入 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. 深入了解模組¶
模組可以包含可執行的陳述式以及函式的定義。這些陳述式是作為模組的初始化,它們只會在第一次被 import 時才會執行。[1](如果檔案被當成腳本執行,也會執行它們)。
每個模組都有它自己的私有命名空間 (namespace),模組內定義的函式會把該模組的私有符號表當成全域命名空間使用。因此,模組的作者可以在模組中使用全域變數,而不必擔心和使用者的全域變數發生意外的名稱衝突。另一方面,如果你知道自己在做什麼,你可以用這個方式取用模組的全域變數,以和引用函式一樣的寫法,modname.itemname
。
在一個模組中可以 import 其他模組。把所有的 import
陳述式放在模組(就這邊來說,腳本也是一樣)的最開頭是個慣例,但並沒有強制。如放置在模組的最高層(不在任何函式或 class 中),被 import 的模組名稱將被加入全域命名空間中。
import
陳述式有另一種變形寫法,可以直接將名稱從欲 import 的模組,直接 import 至原模組的命名空間中。例如:
>>> from fibo import fib, fib2
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
在 import 之後的名稱會被導入,但定義該函式的整個模組名稱並不會被引入在區域命名空間中(因此,示例中的 fibo
未被定義)。
甚至還有另一種變形寫法,可以 import 模組定義的所有名稱:
>>> from fibo import *
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
這個寫法會 import 模組中所有的名稱,除了使用底線 (_
) 開頭的名稱。大多數情況下,Python 程式設計師不大使用這個功能,因為它在直譯器中引入了一組未知的名稱,並且可能覆蓋了某些你已經定義的內容。
請注意,一般情況下並不建議從模組或套件中 import *
的做法,因為它通常會導致可讀性較差的程式碼。但若是使用它來在互動模式中節省打字時間,則是可以接受的。
如果模組名稱後面出現 as
,則 as
之後的名稱將直接和被 import 模組綁定在一起。
>>> import fibo as fib
>>> fib.fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
這個 import 方式和 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
備註
出於效率原因,每個模組在每個直譯器 session 中僅會被 import 一次。因此,如果你更改了模組,則必須重啟直譯器——或者,如果只是一個想要在互動模式下測試的模組,可以使用 importlib.reload()
。例如:import importlib; importlib.reload(modulename)
。
6.1.1. 把模組當作腳本執行¶
當使用以下內容運行 Python 模組時:
python fibo.py <arguments>
如同使用 import 指令,模組中的程式碼會被執行,但 __name__
被設為 "__main__"
。這意味著,透過在模組的末尾添加以下程式碼:
if __name__ == "__main__":
import sys
fib(int(sys.argv[1]))
你可以將檔案作為腳本也同時可以作為被 import 的模組,因為剖析 (parse) 命令列的程式碼只會在當模組是「主」檔案時,才會執行:
$ python fibo.py 50
0 1 1 2 3 5 8 13 21 34
如果此模組是被 import 的,則該段程式碼不會被執行:
>>> import fibo
>>>
這通常是用來為模組提供方便的使用者介面,或者用於測試目的(執行測試套件時,以腳本的方式執行模組)。
6.1.2. 模組的搜尋路徑¶
Import 一個名為 spam
的模組時,直譯器首先會搜尋具有該名稱的內建模組。模組名稱列在 sys.builtin_module_names
當中。如果找不到,接下來會在變數 sys.path
所給定的資料夾清單之中,搜尋一個名為 spam.py
的檔案。sys.path
從這些位置開始進行初始化:
輸入腳本所位在的資料夾(如未指定檔案時,則是目前資料夾)。
PYTHONPATH
(一連串和 shell 變數PATH
的語法相同的資料夾名稱)。與安裝相關的預設值(按慣例會包含一個
site-packages
資料夾,它是由site
模組所處理)。
在 sys.path 模組搜尋路徑的初始化 有更多的細節。
備註
在支援符號連結 (symlink) 的檔案系統中,輸入腳本的所在資料夾是在跟隨符號連結之後才被計算的。換言之,包含符號連結的資料夾並沒有增加到模組的搜尋路徑中。
初始化之後,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 在兩種情況下不檢查快取 (cache)。首先,它總是重新編譯且不儲存直接從命令列載入的模組的結果。第二,如果沒有源模組,則不會檢查快取。要支援非源模組(僅編譯)的發布,編譯後的模組必須位於原始資料夾中,並且不能有源模組。
一些給專家的秘訣:
可以在 Python 指令上使用開關參數 (switch)
-O
或-OO
來減小已編譯模組的大小。開關參數-O
刪除 assert(斷言)陳述式,而-OO
同時刪除 assert 陳述式和 __doc__ 字串。由於有些程式可能依賴於上述這些內容,因此只有在你知道自己在做什麼時,才應使用此參數。「已優化」模組有opt-
標記,且通常較小。未來的版本可能會改變優化的效果。讀取
.pyc
檔案時,程式的執行速度並不會比讀取.py
檔案快。唯一比較快的地方是載入的速度。模組
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
是一個字串 list,它決定直譯器的模組搜尋路徑。它的初始值為環境變數 PYTHONPATH
中提取的預設路徑,或是當 PYTHONPATH
未設定時,從內建預設值提取。你可以用標準的 list 操作修改該變數:
>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')
6.3. dir()
函式¶
內建函式 dir()
用於找出模組定義的所有名稱。它回傳一個排序後的字串 list:
>>> 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. 套件 (Package)¶
套件是一種使用「點分隔模組名稱」組織 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
...
Import 套件時,Python 會搜尋 sys.path
裡的目錄,尋找套件的子目錄。
目錄中必須含有 __init__.py
檔案,才會被 Pyhon 當成套件(除非有使用 namespace package,為一個相對進階的功能);這樣可以避免一些以常用名稱命名(例如 string
)的目錄,無意中隱藏了較晚出現在模組搜尋路徑中的有效模組。在最簡單的情況,__init__.py
可以只是一個空白檔案;但它也可以執行套件的初始化程式碼,或設置 __all__
變數,之後會詳述。
套件使用者可以從套件中 import 個別模組,例如:
import sound.effects.echo
這樣就載入了子模組 sound.effects.echo
。引用時必須用它的全名:
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
另一種 import 子模組的方法是:
from sound.effects import echo
這段程式碼一樣可以載入子模組 echo
,並且不加套件前綴也可以使用,因此能以如下方式使用:
echo.echofilter(input, output, delay=0.7, atten=4)
另一種變化是直接 import 所需的函式或變數:
from sound.effects.echo import echofilter
同樣地,這樣也會載入子模組 echo
,但它的函式 echofilter()
就可以直接使用:
echofilter(input, output, delay=0.7, atten=4)
請注意,使用 from package import item
時,item 可以是套件的子模組(或子套件),也可以是套件中被定義的名稱,像是函式、class (類別)或變數。import
陳述式首先測試套件中有沒有定義該 item;如果沒有,則會假設它是模組,並嘗試載入。如果還是找不到 item,則會引發 ImportError
例外。
相反地,使用 import item.subitem.subsubitem
語法時,除了最後一項之外,每一項都必須是套件;最後一項可以是模組或套件,但不能是前一項中定義的 class、函式或變數。
6.4.1. 從套件中 import *¶
當使用者寫下 from sound.effects import *
時,會發生什麼事?理想情況下,我們可能希望程式碼會去檔案系統,尋找套件中存在的子模組,並將它們全部 import。這會花費較長的時間,且 import 子模組的過程可能會有不必要的副作用,這些副作用只有在明確地 import 子模組時才會發生。
唯一的解法是由套件作者為套件提供明確的索引。import
陳述式使用以下慣例:如果套件的 __init__.py
程式碼有定義一個名為 __all__
的 list,若遇到 from package import *
的時候,它就會是要被 import 的模組名稱。發布套件的新版本時,套件作者可自行決定是否更新此 list。如果套件作者認為沒有人會從他的套件中 import *,他也可能會決定不支援這個 list。舉例來說,sound/effects/__init__.py
檔案可包含以下程式碼:
__all__ = ["echo", "surround", "reverse"]
意思是,from sound.effects import *
將會 import sound.effects
套件中,這三個被提名的子模組。
請注意,子模組可能會被區域定義 (locally defined) 的名稱遮蔽。例如,如果你在 sound/effects/__init__.py
檔案中新增了一個 reverse
函式,則 from sound.effects import *
只會引入兩個子模組 echo
和 surround
,但不是 reverse
子模組,因為它被區域定義的 reverse
函式遮蔽了:
__all__ = [
"echo", # 指向 'echo.py' 檔案
"surround", # 指向 'surround.py' 檔案
"reverse", # !!! 現在指向 'reverse' 函式 !!!
]
def reverse(msg: str): # <-- 在 'from sound.effects import *' 的情況下
return msg[::-1] # 這個名稱遮蔽了 'reverse.py' 子模組
如果 __all__
沒有被定義,from sound.effects import *
陳述式並不會把 sound.effects
套件中所有子模組都 import 到目前的命名空間;它只保證 sound.effects
套件有被 import(可能會運行 __init__.py
中的初始化程式碼),然後 import 套件中被定義的全部名稱。這包含 __init__.py
定義(以及被明確載入的子模組)的任何名稱。它也包括任何之前被 import
陳述式明確載入的套件子模組。請看以下程式碼:
import sound.effects.echo
import sound.effects.surround
from sound.effects import *
此例中,當 from...import
陳述式被執行時,echo
和 surround
模組被 import 進目前的命名空間,因為它們是在 sound.effects
套件裡定義的。(當 __all__
有被定義時,這規則也有效。)
雖然,有些特定模組的設計,讓你使用 import *
時,該模組只會輸出遵循特定樣式的名稱,但在正式環境 (production) 的程式碼中這仍然被視為是不良習慣。
記住,使用 from package import specific_submodule
不會有任何問題!實際上,這是推薦用法,除非 import 的模組需要用到的子模組和其他套件的子模組同名。
6.4.2. 套件內引用¶
當套件的結構為多個子套件的組合時(如同範例中的 sound
套件),可以使用「絕對 (absolute) import」,引用同層套件中的子模組。例如,要在 sound.filters.vocoder
模組中使用 sound.effects
中的 echo
模組時,可以用 from sound.effects import echo
。
你也可以用 from module import name
的 import 陳述式,編寫「相對 (relative) import」。這些 import 使用前導句號指示相對 import 中的目前套件和母套件。例如,在 urround
模組中,你可以使用:
from . import echo
from .. import formats
from ..filters import equalizer
Note that relative imports are based on the name of the current module's package. Since the main module does not have a package, modules intended for use as the main module of a Python application must always use absolute imports.
6.4.3. 多目錄中的套件¶
套件也支援一個特殊屬性 __path__
。它在初始化時是一個字串的序列的 __init__.py
檔案所在的目錄名稱,初始化時機是在這個檔案的程式碼被執行之前。這個變數可以被修改,但這樣做會影響將來對套件內的模組和子套件的搜尋。
雖然這個特色不太常被需要,但它可用於擴充套件中的模組集合。
註解