管理模組化Python程式: 從獨立Project到Package或Submodule

管理模組化Python程式: 從獨立Project到Package或 Submodule

在軟體開發中,有許多知名的法則如物件導向原則、SOLID 原則等。而這些法則其實都是基於完成一個共同的目標-生產乾淨且可重複利用的程式碼。隨著軟體專案模組的開發,會遇到的就是在不同模組之間存在一些共用的模組,或者說程式碼。這時候如何達到程式碼的隔離,並有效地管理共用模組版本是很至關緊要的。

在 Python中,可以使用的方法就是利用git原生的 submodule來達成程式碼的隔離與版號的控管,適合仍處於開發改動密集的子模組。另一個方法則是打包為 Python package直接提供安裝,公開專案如 Pytorch、Numpy、Pandas等等都是可以走這樣的形式,適合開發已有穩定規模,以定期放版更新的模組。

Cover made with Canva. (圖片來源)

Tip

文章難度:★★☆☆☆

閱讀建議: 這篇文章偏向初階到中階 Python程式指引,主要在理解程式碼架構設計、功能隔離等原則後,如何進一步共用一些設計好的模組。文章前半段介紹兩種針對不同時期推薦的管理方式,後半部則是著重在基本的 Python Packaging教學。

推薦背景知識:Python, Programming, Object-Oriented Programming (OOP).

管理Python Module

舉個例子來說,假設我們正在開發兩組模型,分別是是 detection與 segmentation model。而這兩個模型中,有些程式碼是共用的,比如說使用開源的 Numpy,以及內部自己開發的 Logging模組。

兩個專案間有一些重複使用的模組。

先假設我們在程式設計上已經對 function interface、物件隔離等事項做好良好規劃。換句話說,我們已經有了模組化的程式碼。接下來要考慮的事情就是如何系統化地管理這些 Python module,並且讓其他專案或使用者可以有效率地安裝

這時候我們就會希望管理內部開發 Logging模組可以有自己的版本控制,然後使用他的程式碼只要按照版本去安裝或設定 Logging模組即可。通常在 Python上要做到這件事情有兩種做法:

  • Git submodule: 利用 git原生引用和管理第三方資源的方法,以達到程式碼的隔離。通常開發較密集的模組會建議採用這個方法
  • Python package: 打包成可以直接安裝的 Python package,這樣一樣可以達到程式碼隔離,但這些程式碼安裝時會被放到 Python site-packages,要開發、改動時通常就需要去原本的模組開發後,重新安裝。通常模組開發進入穩定期後幾乎都會採用這個方法

Git submodule是 git原生引用和管理第三方資源的方法,通常用於開發前期、改動較密集的模組

假設現在有一個專案,裡面包含一個開發中的 package:

這時候如果希望 project與 package有獨立的版本控制,一個方法就是 project不記錄 module的實際內容,僅記錄 module的版本號。這時候就是使用 submodule的觀念,管理 package的資源。

Git submodule的觀念。(資料來源)

通常我們會先把這個 module拉出來,獨立建立成 git,並 commit、 push到 remote server上,再回到原本的project上,把 module用 submodule的方法加回來。

這時候專案看起來會跟原本長的一模一樣,所有針對 package的更新也可以直接 commit & push,不過就是推到不同的 git上。

如果要查看所有 submodule的狀況,通常會下:

不過也要注意的是,如果專案是用 submodule維護,在第一次 clone下來時,記得要初始化 submodule。

另外一個常見的方式就是打包成 Python Package,這些打包後的 package安裝設定上都會更為簡便,基本上就是直接pip install。

Python Package通常是用於較穩定,且有 release控管的 package

通常 Python檔案可以分成三個階段:

  • 第一個階段是純 Python檔或是具有__init__.py的資料夾,通常就稱為 Python standalone modules。
  • 第二階階段就是source distribution,也就是所謂的原始碼包,通常檔案結尾會是.tar.gz。
  • 第三個階段則是預編譯好的 build distribution,俗稱輪子,通常檔案結尾會是.whl。
Python Package的分層。(資料來源)

通常一個的Python Package原始碼的基本格式應該會長這樣:

接下來通常會加上一些其他檔案,如使用授權的LICENSE、專案說明文件的 README.md**,**定義 Python project資訊的 pyproject.toml以及放測試檔的 `tests等等。

Simple Packaging Tutorial

接下來介紹一下如何實際實際打包一個Python套件。整體上其實沒什麼特別困難的,許多細節可以在需要使用時再慢慢學習。簡單來說,步驟大概是:

  1. 整理好程式碼 (廢話)。
  2. 準備project資料與安裝設定。
  3. Build package。
  4. (Optional) 上傳到可直接安裝的server (如PyPi)。
  5. (Optional) 建立自動化測試與發佈流程。

以下簡單看一個很沒用的範例,了解一下如何建立 package。

首先先隨便寫個很基本的功能,比如說寫個裝飾器來計算 function的運行時間。

接下來把該有的文件準備好,以下用pyproject.toml做為安裝設定範例。

  • 原始碼 (必須): 存放 source code的地方,如前述的src或這邊的package_tutorial_timer。
  • 安裝設定 (必須):常使用pyproject.toml或是setup.py(也可以都有),稍後解釋內容。
  • 授權 (非必須):通常第三方程式都會有對應的使用規範,常見開源的使用規範如BSD、 Apache、 GPL、 MIT等等。
  • 使用說明書 (非必須): 說明如何使用這支程式庫的 interface,通常簡單的就寫個README.md,複雜的就會寫自己的專案 documentation page。
  • 測試程式 (非必須):針對這個程式庫的 testing,避免更版時產生顯而易見的 bugs,在自動化 CI/CD流程是很重要的一環。

以單純使用pyproject.toml設定 package為例。裡面大概必須包含以下項目,如build package的 backend、模組名稱、版號、相依等等

另一個寫法是setup.py,我個人比較常採用這個方法,以下是我自己通常會寫的 script:

其實 Python設定 package也不止這兩種,也有使用 setup.cfg或其他做法,亦可以混用,或是近一步透過其他管理工具管理的。

基本上做到這一步就可以直接使用 pip或其他 Python套件管理工具安裝,或是上傳到公開 github上都可以達成讓人直接下載的目的。

不過前面有提到通常給人家安裝的檔案是 source distribution或是 build distribution,也就是說前面的 Python Package也可以更進一步地 build起來,讓其他使用者安裝更快捷。

上面這個指令會同時建立 source distribution或是 build distribution,當然也可以單純建立其中之一,或是調整 build細節 (如設定為Universal wheelPlatform wheel 等等)

通常使用 pip install時,預設就是從 PyPI上拉 package下來安裝。 PyPI全名是 Python Package Index,是Python的正式第三方 (official third-party) 軟體套件的軟體存儲庫。因此 PyPI也是 Python最常見上傳 source distribution與 buid distribution的地方。

The Python Package Index。(資料來源)

最簡單上傳到 PyPI的方法就是先上去註冊個帳號,然後使用 twine上傳。 Twine是在不同系統上都可以使用,簡單將 Python packages推到 PyPI上,並進行更版的行為。實際上使用就是安裝,然後一行 upload搞定:

當然這些行為後期通常都不會手動來做,會綁定許多 testing機制,將測試、打包、上傳這些動作透過自動化 CI/CD工具部屬,如 Github action workflow或 GitLab CI/CD pipeline。

好了~這篇文章就先到這邊。其實建立一個好用的 package可以說的實在太多了,光程式設計的原理,到 interface與架構的規劃就已經是有夠大的題目了,而這也是最根本的東西。打包、發包比較偏向是工具的部分,也建議大家長期以來要善用測試、自動 CI/CD來簡化發布的動作。之後有機會再來簡單寫一個如何用 Github action來依照 tag或 release執行自動發布。

老話一句,軟體開發領域每年都會有許多新語法、方法或工具的變化,說真的要跟緊不是一件容易的事。所以我的觀點可能也會存在瑕疵,若有發現什麼錯誤或值得討論的地方,歡迎回覆文章或來信一起討論 :)

Reference

  1. Python Packaging User Guide[python.org]
comments powered by Disqus