2.19. 撰寫模組¶
任何 .py 檔案都是一個模組。把一個逐漸變大的指令碼拆分到幾個檔案中,能讓每個檔案保持簡短,並讓共用的輔助程式能在指令碼之間共享。
2.19.1. 拆分指令碼¶
把一組相關的函式抽出,放進它們自己的檔案:
camera_utils.py::
def banner():
print("OpenMV")
def label(text):
return "[" + text + "]"
main.py::
import camera_utils
camera_utils.banner()
print(camera_utils.label("ready"))
輸出::
OpenMV
[ready]
這兩個檔案並排放在同一個目錄中。當 main.py 執行時,import camera_utils 會讀取 camera_utils.py 一次、執行它的頂層述句,並把產生的模組物件繫結到 main 中的名稱 camera_utils。從其他任何地方再次 import camera_utils 都會回傳同一個物件 -- 模組在第一次載入後就會被快取。
模組的名稱來自它的檔名,所以 camera_utils.py 是以 import camera_utils 來引入的。
2.19.2. 多檔案模組(套件)¶
一個模組也可以是一個檔案的 目錄,而非單一的 .py。該目錄的名稱會成為模組名稱,而裡面的檔案則是它的 子模組:
camera_utils/
__init__.py
text.py
timing.py
__init__.py 是套件本身被引入時會執行的檔案;它可以是空的,也可以從子模組重新匯出選定的名稱。子模組以帶點的名稱來取用:
import camera_utils.text
from camera_utils.timing import elapsed
camera_utils.text.label("ready")
在套件內部,子模組彼此之間既可以用完整的帶點名稱、也可以用一個以開頭的點代表「這個套件」的 相對 import 來互相取用:
camera_utils/timing.py::
from . import text # the sibling submodule
def stamp(value):
return text.label(str(value))
若要改為引入某個特定名稱,請在帶點的同層手足名稱後指明它:
from .text import label
def stamp(value):
return label(str(value))
相對 import 能讓套件保持自我包含:重新命名套件目錄並不需要去編輯每一個子模組。
當單一檔案大到超過了舒適的尺寸,或當一組相關模組理應一同歸屬於同一個命名空間之下時,就使用套件。對於日常的指令碼,單一一個 .py 檔案就夠了。
2.19.3. __name__ 守衛¶
每個模組都有一個內建名稱 __name__。它的值取決於該檔案是如何被使用的:
當該檔案 被直接執行 時,
__name__會被設為字串"__main__"。當該檔案 被另一個指令碼引入 時,
__name__會被設為模組名稱 -- 也就是不含.py的檔名。
使用這一點的慣用寫法是:
label_util.py::
def label(text):
return "[" + text + "]"
if __name__ == "__main__":
print(label("self-test"))
輸出(直接執行時)::
[self-test]
當同一個檔案改為 被引入 時,__name__ 會被設為模組名稱,因此 if 區塊會被略過,不會有額外的東西執行:
>>> import label_util
>>> label_util.__name__
'label_util'
用這個模式為某個函式庫檔案附上一個快速的冒煙測試或示範,而不會干擾到引入它的指令碼。