使用套件
May 8, 2022現在你撰寫的程式碼,可以分別放在各模組之中,就原始碼管理上比較好一些了,但還不是很好,就如同你會分不同資料夾,放置不同作用的檔案,模組也應該分門別類加以放置。
建立套件
舉例來說,一個應用程式中會有多個類別彼此合作,也有可能由多個團隊共同分工,完成應用程式的某些功能塊,再組合在一起,如果應用程式是多個團隊共同合作,卻不分門別類放置模組,那麼 A 部門寫了個 util
模組,B 部門也寫了個 util
模組,當他們要將應用程式整合時,想將模組都放在同一個lib目錄中的話,就會發生同名的util.py檔案覆蓋的問題。
兩個部門各自建立資料夾放置自己的 util.py 檔案,然後在 PYTHONPATH
設定路徑的方式行不通,因為執行 import util
時,只會使用 PYTHONPATH
第一個找到的 util.py,你真正需要的方式,必須是能夠 import a.util
或 import 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.util
與 import b.util
,若想取用各自模組中的名稱,也可以使用 a.util.some
、b.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
模組,而不是 pkg1
的 abc
模組。
如果想在 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.abc
、pkg2.mno
、pkg1.xyz
來使用模組了,同樣的手法也可應用在套件中還有子套件的情況。例如,若有個套件與模組階層如下:
pkg1/
__init__.py
abc.py
mno.py
xyz.py
sub_pkg/
__init__.py
foo.py
orz.py
如果想要 import pkg1
之後,可以直接使用 pkg1.abc
、pkg2.mno
、pkg1.xyz
模組,而且還能直接使用 pkg1.sub_pkg.foo
、pkg1.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