6.模組 (Module)¶
如果從 Python 直譯器離開後又再次進入,之前(幫函式或變數)做的定義都會消失。因此,想要寫一些比較長的程式時,你最好使用編輯器來準備要輸入給直譯器的內容,並且用該檔案來運行它。這就是一個腳本 (script)。隨著你的程式越變越長,你可能會想要把它分開成幾個檔案,讓它比較好維護。你可能也會想用一個你之前已經在其他程式寫好的函式,但不想要複製該函式的原始定義到所有使用它的程式裡。
為了支援這一點,Python 有一種方法可以將定義放入檔案中,並在互動模式下的直譯器中使用它們。這種檔案稱為模組 (module);模組中的定義可以被import 到其他模組中,或是被import 至主 (main) 模組(在最頂層執行的腳本,以及互動模式下,所使用的變數集合)。
模組是指包含 Python 定義和語句的檔案,檔案名稱是模組名稱加上.py
。在模組中,模組的名稱(作為字串)會是全域變數__name__
的值。例如,用你喜歡的文字編輯器在資料夾中創一個名為fibo.py
的檔案,內容如下:
# 費波那契數模組deffib(n):# 寫出費波那契數列至第 n 位a,b=0,1whilea<n:print(a,end=' ')a,b=b,a+bprint()deffib2(n):# 回傳費波那契數列至第 n 位result=[]a,b=0,1whilea<n:result.append(a)a,b=b,a+breturnresult
現在進入 Python 直譯器並用以下指令 import 這個模組:
>>>importfibo
這並不會將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 至原模組的命名空間中。例如:
>>>fromfiboimportfib,fib2>>>fib(500)0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
在 import 之後的名稱會被導入,但定義該函式的整個模組名稱並不會被引入在區域命名空間中(因此,示例中的fibo
未被定義)。
甚至還有另一種變形寫法,可以 import 模組定義的所有名稱:
>>>fromfiboimport*>>>fib(500)0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
這個寫法會 import 模組中所有的名稱,除了使用底線 (_
) 開頭的名稱。大多數情況下,Python 程式設計師不大使用這個功能,因為它在直譯器中引入了一組未知的名稱,並且可能覆蓋了某些你已經定義的內容。
請注意,一般情況下並不建議從模組或套件中 import*
的做法,因為它通常會導致可讀性較差的程式碼。但若是使用它來在互動模式中節省打字時間,則是可以接受的。
如果模組名稱後面出現as
,則as
之後的名稱將直接和被 import 模組綁定在一起。
>>>importfiboasfib>>>fib.fib(500)0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
這個 import 方式和importfibo
實質上是一樣的,唯一的差別是現在要用fib
使用模組。
在使用from
時也可以用同樣的方式獲得類似的效果:
>>>fromfiboimportfibasfibonacci>>>fibonacci(500)0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
備註
出於效率原因,每個模組在每個直譯器 session 中僅會被 import 一次。因此,如果你更改了模組,則必須重啟直譯器——或者,如果只是一個想要在互動模式下測試的模組,可以使用importlib.reload()
。例如:importimportlib;importlib.reload(modulename)
。
6.1.1.把模組當作腳本執行¶
當使用以下內容運行 Python 模組時:
pythonfibo.py<arguments>
如同使用 import 指令,模組中的程式碼會被執行,但__name__
被設為"__main__"
。這意味著,透過在模組的末尾添加以下程式碼:
if__name__=="__main__":importsysfib(int(sys.argv[1]))
你可以將檔案作為腳本也同時可以作為被 import 的模組,因為剖析 (parse) 命令列的程式碼只會在當模組是「主」檔案時,才會執行:
$pythonfibo.py500 1 1 2 3 5 8 13 21 34
如果此模組是被 import 的,則該段程式碼不會被執行:
>>>importfibo>>>
這通常是用來為模組提供方便的使用者介面,或者用於測試目的(執行測試套件時,以腳本的方式執行模組)。
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
則用來定義主、次提示字元的字串:
>>>importsys>>>sys.ps1'>>> '>>>sys.ps2'... '>>>sys.ps1='C> 'C> print('Yuck!')Yuck!C>
只有直譯器在互動模式時,才需要定義這兩個變數。
變數sys.path
是一個字串 list,它決定直譯器的模組搜尋路徑。它的初始值為環境變數PYTHONPATH
中提取的預設路徑,或是當PYTHONPATH
未設定時,從內建預設值提取。你可以用標準的 list 操作修改該變數:
>>>importsys>>>sys.path.append('/ufs/guido/lib/python')
6.3.dir()
函式¶
內建函式dir()
用於找出模組定義的所有名稱。它回傳一個排序後的字串 list:
>>>importfibo,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]>>>importfibo>>>fib=fibo.fib>>>dir()['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']
請注意,它列出所有類型的名稱:變數、模組、函式等。
dir()
不會列出內建函式和變數的名稱。如果你想要列出它們,它們被定義在標準模組builtins
內:
>>>importbuiltins>>>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 個別模組,例如:
importsound.effects.echo
這樣就載入了子模組sound.effects.echo
。引用時必須用它的全名:
sound.effects.echo.echofilter(input,output,delay=0.7,atten=4)
另一種 import 子模組的方法是:
fromsound.effectsimportecho
這段程式碼一樣可以載入子模組echo
,並且不加套件前綴也可以使用,因此能以如下方式使用:
echo.echofilter(input,output,delay=0.7,atten=4)
另一種變化是直接 import 所需的函式或變數:
fromsound.effects.echoimportechofilter
同樣地,這樣也會載入子模組echo
,但它的函式echofilter()
就可以直接使用:
echofilter(input,output,delay=0.7,atten=4)
請注意,使用frompackageimportitem
時,item 可以是套件的子模組(或子套件),也可以是套件中被定義的名稱,像是函式、class (類別)或變數。import
陳述式首先測試套件中有沒有定義該 item;如果沒有,則會假設它是模組,並嘗試載入。如果還是找不到 item,則會引發ImportError
例外。
相反地,使用importitem.subitem.subsubitem
語法時,除了最後一項之外,每一項都必須是套件;最後一項可以是模組或套件,但不能是前一項中定義的 class、函式或變數。
6.4.1.從套件中 import *¶
當使用者寫下fromsound.effectsimport*
時,會發生什麼事?理想情況下,我們可能希望程式碼會去檔案系統,尋找套件中存在的子模組,並將它們全部 import。這會花費較長的時間,且 import 子模組的過程可能會有不必要的副作用,這些副作用只有在明確地 import 子模組時才會發生。
唯一的解法是由套件作者為套件提供明確的索引。import
陳述式使用以下慣例:如果套件的__init__.py
程式碼有定義一個名為__all__
的 list,若遇到frompackageimport*
的時候,它就會是要被 import 的模組名稱。發布套件的新版本時,套件作者可自行決定是否更新此 list。如果套件作者認為沒有人會從他的套件中 import *,他也可能會決定不支援這個 list。舉例來說,sound/effects/__init__.py
檔案可包含以下程式碼:
__all__=["echo","surround","reverse"]
意思是,fromsound.effectsimport*
將會 importsound.effects
套件中,這三個被提名的子模組。
請注意,子模組可能會被區域定義 (locally defined) 的名稱遮蔽。例如,如果你在sound/effects/__init__.py
檔案中新增了一個reverse
函式,則fromsound.effectsimport*
只會引入兩個子模組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 !!!]defreverse(msg:str):# <-- this name shadows the 'reverse.py' submodulereturnmsg[::-1]# in the case of a 'from sound.effects import *'
如果__all__
沒有被定義,fromsound.effectsimport*
陳述式並不會把sound.effects
套件中所有子模組都 import 到目前的命名空間;它只保證sound.effects
套件有被 import(可能會運行__init__.py
中的初始化程式碼),然後 import 套件中被定義的全部名稱。這包含__init__.py
定義(以及被明確載入的子模組)的任何名稱。它也包括任何之前被import
陳述式明確載入的套件子模組。請看以下程式碼:
importsound.effects.echoimportsound.effects.surroundfromsound.effectsimport*
此例中,當from...import
陳述式被執行時,echo
和surround
模組被 import 進目前的命名空間,因為它們是在sound.effects
套件裡定義的。(當__all__
有被定義時,這規則也有效。)
雖然,有些特定模組的設計,讓你使用import*
時,該模組只會輸出遵循特定樣式的名稱,但在正式環境 (production) 的程式碼中這仍然被視為是不良習慣。
記住,使用frompackageimportspecific_submodule
不會有任何問題!實際上,這是推薦用法,除非 import 的模組需要用到的子模組和其他套件的子模組同名。
6.4.2.套件內引用¶
當套件的結構為多個子套件的組合時(如同範例中的sound
套件),可以使用「絕對 (absolute) import」,引用同層套件中的子模組。例如,要在sound.filters.vocoder
模組中使用sound.effects
中的echo
模組時,可以用fromsound.effectsimportecho
。
你也可以用frommoduleimportname
的 import 陳述式,編寫「相對 (relative) import」。這些 import 使用前導句號指示相對 import 中的目前套件和母套件。例如,在urround
模組中,你可以使用:
from.importechofrom..importformatsfrom..filtersimportequalizer
請注意,相對 import 的運作是以目前的模組名稱為依據。因為主模組的名稱永遠是"__main__"
,所以如果一個模組預期被用作 Python 應用程式的主模組,那它必須永遠使用絕對 import。
6.4.3.多目錄中的套件¶
套件也支援一個特殊屬性__path__
。它在初始化時是一個字串的序列的__init__.py
檔案所在的目錄名稱,初始化時機是在這個檔案的程式碼被執行之前。這個變數可以被修改,但這樣做會影響將來對套件內的模組和子套件的搜尋。
雖然這個特色不太常被需要,但它可用於擴充套件中的模組集合。
註解
[1]實際上,函式定義也是「被執行」的「陳述式」;在執行模組階層的函式定義時,會將函式名稱加到模組的全域命名空間。