使用套件

May 8, 2022

現在你撰寫的程式碼,可以分別放在各模組之中,就原始碼管理上比較好一些了,但還不是很好,就如同你會分不同資料夾,放置不同作用的檔案,模組也應該分門別類加以放置。

建立套件

舉例來說,一個應用程式中會有多個類別彼此合作,也有可能由多個團隊共同分工,完成應用程式的某些功能塊,再組合在一起,如果應用程式是多個團隊共同合作,卻不分門別類放置模組,那麼 A 部門寫了個 util 模組,B 部門也寫了個 util 模組,當他們要將應用程式整合時,想將模組都放在同一個lib目錄中的話,就會發生同名的util.py檔案覆蓋的問題。

兩個部門各自建立資料夾放置自己的 util.py 檔案,然後在 PYTHONPATH 設定路徑的方式行不通,因為執行 import util 時,只會使用 PYTHONPATH 第一個找到的 util.py,你真正需要的方式,必須是能夠 import a.utilimport b.util 來取用對應的模組。

假設你想在 hello_prj 專案資料夾中,新增一個 openhome 套件,那麼請在 hello_prj 建立一個 openhome 資料夾。

在 Python 3.2 以前,資料夾中一定要有一個 __init__.py 檔案,該資料夾才會被視為一個套件,在套件的進階管理中,__init__.py 會用來撰寫程式,如果沒有要撰寫的程式,也可以保持檔案內容為空。

如果有個 hello.py 放在 openhome 資料夾中,那麼 hello_prj 資料夾裡的 .py 要取用這個模組,就必須使用 import openhome.hello,更具體地說,當 python 直譯器看到 import openhome.hello 時,會尋找 sys.path 中的路徑裏,是否有某個資料夾中含有 openhome 資料夾,若有就確認有 openhome 套件了,接著看看其中是否有 hello.py,如果找到,就可以順利完成模組的 import

也就是模組名稱前被加上了套件名稱,這就說明了,套件名稱會成為名稱空間的一部份。

由於套件名稱會成為名稱空間的一部份,就先前 A、B 兩部門的例子來說,可以分別建立 a 套件與 b 套件,當中放置各自的 util.py,當兩個部門的 a、b 兩個資料夾放到同一個 lib 資料夾時,並不會發生 util.py 檔案彼此覆蓋的問題,而在取用模組時,可以分別 import a.utilimport b.util ,若想取用各自模組中的名稱,也可以使用 a.util.someb.util.other 來區別。

如果模組數量很多,也可以建立多層次的套件,也就是套件中還會有套件。同樣地,在 Python 3.2 以前,若想建立 openhome.blog 套件,那麼 openhome 資料夾中要有個 __init__.py 檔案,而 openhome/blog 資料夾中,也要有個 __init__.py 檔案。

相對匯入

到目前為止使用的匯入方式,都是絕對匯入(Abstract import),也就是完整指定套件與模組的完整名稱。Python 還支援相對匯入(Relative import)。舉個例子來說,如果有個套件結構如下:

pkg1/
    __init__.py
    abc.py
    mno.py
    xyz.py

若想在 xyz.py 匯入 abc 模組,xyz.py 中不能寫 import abc,因為這會是絕對匯入,會匯入標準程式庫的 abc 模組,而不是 pkg1abc 模組。

如果想在 xyz.py 匯入 mno 模組,xyz.py 中不能寫 import mno,這會引發 ImportError,指出沒有 mno 這個模組,如果要使用絕對匯入,必須撰寫 import pkg1.mno,若使用相對匯入,可以撰寫 from . import mno

如果想使用的 mno 模組的 foo 函式,使用相對匯入的話,可以撰寫 from .mno import foo 這樣的方式,這麼一來,就可以直接使用 foo 來呼叫了。

相對匯入的使用,還可以讓套件中的模組在使用時更為方便,例如,在某個程式中,若只是 import pkg1 的話,只會執行 __init__.py 的內容,沒辦法直接使用 pkg1.abc 模組或其他模組,若想使用 pkg1.abc 模組,必須再進行一次 import pkg1.abc 才可以,如果想在 import pkg1 時,就能直接使用 pkg1.abc 模組或套件中其他模組,可以在 pkg1 的 __init__.py 中撰寫:

from . import abc
from . import mno
from . import xyz

這麼一來的話,只要 import pkg1,就可以撰寫 pkg1.abcpkg2.mnopkg1.xyz 來使用模組了,同樣的手法也可應用在套件中還有子套件的情況。例如,若有個套件與模組階層如下:

pkg1/
    __init__.py
    abc.py
    mno.py
    xyz.py
    sub_pkg/
        __init__.py
            foo.py
            orz.py

如果想要 import pkg1 之後,可以直接使用 pkg1.abcpkg2.mnopkg1.xyz 模組,而且還能直接使用 pkg1.sub_pkg.foopkg1.sub_pkg.orz 模組,那麼在 pkg1 的 __init__.py 可以撰寫:

from . import abc
from . import mno
from . import xyz
from . import sub_pkg

而在 pkg1 的 sub_pkg 中 __init__.py 可以撰寫:

from . import foo
from . import orz

有一點小小的麻煩是,相對匯入只能用在套件中,如果試圖使用 python 直譯器執行的某個模組中含有相對匯入,會引發 SystemError,例如,若 xyz.py 中撰寫了:

from . import mno

def do_demo():
    mno.demo()

if __name__ == '__main__':
    do_demo()

直接使用 python xyz.py 執行時,將會有以下的錯誤:

raceback (most recent call last):
  File "xyz.py", line 1, in <module>
    from . import mno
SystemError: Parent module '' not loaded, cannot perform relative import

分享到 LinkedIn 分享到 Facebook 分享到 Twitter