Skip to content

module

Python 模組與套件 – module, package in Python

在開始前,會需要先具備Scope的基本觀念:Python變數範圍 – Scope


Module

module只是物件,來自特定的python資料型別types.ModuleType

import做了什麼事?

Python會直接從系統找尋已經存在的cache,若找不到此模組,才會繼續以下操作:
1. 建構一個ModuleType物件
2. 將此物件動態(in run time)載入記憶體
3. 將此物件的關聯被加到global namespace,可透過globals()訪問
4. 將物件的關聯加到系統cache,可透過sys.modules訪問
5. 編譯及執行該模組的source code

用一個最直接的例子來驗證caching(但這樣寫有大麻煩,請勿這樣用):

那以上這些動作可不可以自己來呢?可以!步驟大致上長這樣:

除此之外,也可以透過importlib.import_module來載入

這些模組從哪裡來?

  • built-in
  • 硬碟上的特定檔案,可透過__file__訪問

模組可以來自一般(.py)、預先編譯(.pyc)、壓縮檔(.zip)等…幾乎是沒什麼限制只要finders能找得到

python怎麼找到特定模組?——finders和loaders

Python:「Finder一號,你知道這個module在哪裡嗎?」
Finder一號: 「拍謝,我不知道ㄟ」
Python:「Finder二號,你知道這個module在哪裡嗎?」
Finder二號:「哦哦哦!我知道!來,我給你一張地圖,上面有說去哪找,也有說> 明找到後要交給Loader五號,他會幫你處理滴。」
Python:「Loader五號大大,請你幫我處理我手中這個module。」
Loader五號:「沒問題!」

上面這個對話裡的地圖,就是ModuleSpec(可以透過importlib.util.find_spec來取得)

透過sys.meta_path可以看到目前所有的Finders

所以客製化自定義的finder跟loader是完全可行的,或是自訂一個importer處理所有操作,可以參考Site-specific configuration hook

不同import的方式(variants)

  • from math import sqrt
    1. 使用cache或載入math(一樣載入整個math module),但math不加到global namespace
    2. sqrt(symbol)加到global namespace且reference至math.sqrt
  • from math import sqrt as r_sqrt
    1. 使用cache或載入math(一樣載入整個math module),但math不加到global namespace
    2. r_sqrt(symbol)加到global namespace且reference至math.sqrt
  • from math import *
    1. 使用cache或載入math(一樣載入整個math module),但math不加到global namespace
    2. 將math定義過所有的symbol和各自的reference加到global namespace

對module來說,以上這些變化,其實都是載入整個module,只差在namespace加的變數不同,所以並沒有部份載入這回事(載入package的行為就會不同,可以做到部份載入)

以上這些變化在效率上差別很小,只差在lookup的次數,如math.sqrt(2)sqrt(2),所以還是依照需求來決定怎麼寫,並非from的寫法就一定比較好

值得注意的是,from import *可能會導致錯誤,因為相同名稱的symbol會在namespace中依照先後順序互相覆蓋,舉例來說:

此外,有時會避免symbol撞名導致覆蓋,會選擇在local scope來載入,這樣做的載入效能會差,lookup次數的效能差別也會更為顯著

__name____main__

__name__變數用來顯示當前模組的名稱;如果該模組是程式入口,呼叫__name__的值時Python會回傳__main__字串,因此我們可以藉此模擬一種情境,來判斷當下的模組是否為程式入口:

Package

套件即是模組,但是此模組是由多個模組或子套件封裝而成,也就是目錄(directory)的概念,套件的名稱為目錄名,套件的屬性__path__為目錄的絕對路徑

Python透過__path____file____package__這些屬性來找到、封裝及載入你要的程式碼:

套件的入口__init__.py:

  • 告訴Python:請把這個目錄當作一個套件
  • 套件的__file__屬性即自身__init__.py的絕對路徑

幾個套件namespaces的封裝方法:

  • __init__.py
  • 將變數命名為私有(_)
  • __all__

Namespace package

與一般的套件相比,Namespace package不透過__init__.py封裝(沒有__file__屬性),其中的子套件可以來自不同的目錄位置