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.ps1sys.ps2 則用來定義主、次提示字元的字串:

>>> import sys
>>> sys.ps1
'>>> '
>>>