會注意到這個東西,起因於工作上用到許多 GCP 的 python 套件。像是 bigquery 跟 dataproc。
如果你看它們的安裝過程,會發現它們在 PyPI 上都是分開的 package,像是 google-cloud-bigquery
與google-cloud-dataproc
。可是安裝之後,卻可以像這樣 import 它們:
from google.cloud import bigquery, dataproc_v1
咦? 我們沒有裝一個 package 叫 google
阿? 如果你把 site-packges
打開來看,你會發現 bigquery
跟 dataproc_v1
都被放在 google.cloud
這個 module 下面,彷彿是 google.cloud
的 submodule。如果你本身又有一些包 python package 的經驗,應該已經開始覺得事情不單純了。
setup.py
當初想到這個的時候,我第一時間就去把 google-cloud-bigquery
的 setup.py
翻出來看了:
其實細看後,也沒做什麼太黑魔法的事,不過是用了 namespace package:
https://packaging.python.org/guides/packaging-namespace-packages/
很久以前在 python language reference 就讀過 namespace package 這東西,但一直沒有想通這東西的使用情境,讀了 PEP 420 還是懞懞懂懂,一直到看到 GCP 的 python packages 才想通了用法。
理解源於實踐,讓我們模仿 google-cloud-bigquery
的 setup.py
來寫幾個 namespace package 看看。
譬如說我們想寫兩個 pakcage: dboy-package1
跟 dboy-package2
,而且可以用 from dboy import package1, package2
的方式去 import 。這兩個套件的資料夾可以長這樣:
眼尖的讀者可能發現,為什麼 dboy_package1/dboy
下面沒有 __init__.py
? 想起這幾年面試別人常會聽到有人說一個資料夾要有 __init__.py
才可以被 python import ,通常當下就會被我糾正然後跟他們說說 python language reference 中的 import system 一章。
老實說我真心希望身為 python programmer 應該時不時複習一下 python language reference,幫助真的很大啊…..
好啦,言歸正傳。根據上述的文件,可以知道當資料夾沒有 __init__.py
時,會被當成 native namespace package 處理。接下來在 dboy_package1
的 setup.py
這樣寫:
setup(
name="dboy-package1",
packages=setuptools.PEP420PackageFinder.find(),
namespace_packages=["dboy"],
zip_safe=False
)
沒錯,只需要加上 namespace_packages
這個 keyword argument ,就可以實現從同一個 namespace 去 import 不同 package 這種語法。 dboy-package2
的 setup.py
其實也就大同小異。
但這裡有個陷阱 (奸笑)
眼尖的人應該又會發現,不是說要沒有 __init__.py
才是 native namespace package 嗎? 那為什麼 dboy_package2/dboy
下面又有 __init__.py
了呢?
沒錯,一般來說是這樣,所以有需要在 dboy_pakage2/dboy/__init__.py
做些手腳。裡面的內容是這樣的:
import pkg_resourcespkg_resources.declare_namespace(__name__)
有就是透過 pkg_resources
去宣告某個 module 是 namespace 而已。
安裝之後,我們就可以這樣寫了:
from dboy import package1, package2
而且在 site-packages
中是長這樣的:
package1
跟 package2
彷彿就像嵌入 dboy
這個 package 一樣。
結語
又學會了一個 python 冷技巧 (撒花)
但到底拿來幹嘛呢? 老實說我也不知道。但是我覺得或許是個設計 addon package 的好用功能。
譬如說你有一個核心的 core
,然後你希望根據不同需求去安裝不同擴充功能,然後都被歸在 core
底下時,就可以用 namespace package 去實現。
大概就是這樣囉,Happy Python Programming!