This post is also available in: en

歡迎回到這個系列,終於找到點時間把這篇給完成。 上一篇主要在講如何管理 Python runtime,這篇會介紹一些替你管理專案中套件的工具。

背景

pip 是 CPython 在 3.4 之後預設附帶的套件安裝工具。你可以找到很多教學教你用 pip 跟 requirements.txt 來建立並管理虛擬環境。如果你專案只使用到少數幾個套件或者你不需要長期維護,用 pip 來管理也是完全沒問題。但如果你需要長期維護你的專案並且套件越用越多,你就會發現只用 pip 跟requirements.txt 的時候會有一些問題。

主要問題: Reproducibility

一些人使用 requirements.txt 的方法是把專案中所直接用到的套件 (direct dependencies) 加到 requirements.txt 裡。這個用法的主要問題是你套件可能會改變,你無法保證你下次可以創建一模一樣的環境。即使你固定你所用套件的版本,套件所依賴的套件也有可能會有變動。

一個常見的解法是用 pip freeze 把虛擬環境的所有套件及其版本給導出到 requirements.txt 。但這也有另外一個問題:要如何安全地新增或者刪除套件。例如 pip install 的時候更新了現有套件所依賴的套件,而這有可能導致現有套件產生問題。

如果你有用過其他語言的套件管理工具,你可能會知道大多數的工具會用兩個檔案來管理套件。

  1. 一個放所直接用到的套件譬如說你只用到 numpy 跟 pandas 的話,就會把它們 ( 可能連同版本資訊放到一個檔案裡。
  2. 另外一個通常被稱為 lock file,這個檔案會包含直接用到的套件以及所有會用到的底層套件

我們會需要用版本管理軟體來存放並且分享這兩個檔案,這樣其他的協作者也可透過這兩個檔案來 reproduce 一致的環境。

在 Python 生態系裡面,其實也有一些工具可以提供相似功能。在這篇文章裡面,我會介紹 pip-tools、Pipenv、Poetry 以及 conda-lock。

pip-tools

pip-tools 是一個由 Jazzband所維護的開源專案。它提供了兩個 CLI 來維護你的 Python 環境。

  1. pip-compile 將你的 direct dependencies 編譯成一個requirements.txt 檔。
  2. pip-sync 可以根據編譯出的 requirements.txt 來更新你的虛擬環境。

基本上 pip-tools 需要跟 virtualenv 一起用然後跟 pip 一樣,它需要被安裝在每個虛擬環境中。

用法

就如上所說,我們需要在每個虛擬環境中先安裝 pip-tools。

// 假設你在使用 pyenv
// 先創立虛擬環境
pyenv virtualenv 3.9.1 pip-tools-test
// Activate
pyenv shell pip-tools-test
// 安裝
python -m pip install pip-tools

也可以參考 pip-tools 的 官方文件

在安裝完成後,我們可以開始用安裝好的兩個指令。 pip-compile 預設的用法是會從 setup.py 會是 requirements.in 讀取專案所需的套件,然後編譯所有的依賴存在requirements.txt 。 假設我們專案需要 Django and Celery ,且我們不希望 Celery 用 5.0 以上的版本。我們可以編輯 requirements.in

# requirements.in
django
celery<5

那如果這個時候執行 pip-compile,所有環境所需要的套件就會被寫到 requirements.txt

#
# This file is autogenerated by pip-compile
# To update, run:
#
#    pip-compile
#
amqp==2.6.1
    # via kombu
asgiref==3.3.1
    # via django
billiard==3.6.3.0
    # via celery
celery==4.4.7
    # via -r requirements.in
django==3.1.7
    # via -r requirements.in
importlib-metadata==3.7.3
    # via kombu
kombu==4.6.11
    # via celery
pytz==2021.1
    # via
    #   celery
    #   django
sqlparse==0.4.1
    # via django
typing-extensions==3.7.4.3
    # via importlib-metadata
vine==1.3.0
    # via
    #   amqp
    #   celery
zipp==3.4.1
    # via importlib-metadata

可以看到套件名外也會 " 鎖住 " 版本號。另外 pip-tools 還幫我們在註解裡面標註說套件的依賴關係是從哪來的。當我們需要新增或刪除套件,可以編輯 requirements.in 然後重新產生 requirements.txt

生成 requirements.txt 之後,就可以執行 pip-sync 更新我們的虛擬環境,會自動地新增或移除虛擬環境裡面的套件。

除此之外,pip-tools 也可以利用多個檔案來支援 dev dependencies 。個人覺得 pip-tools 比下面介紹的工具簡單而且有相容於傳統 Python 環境,可適用於大部分的 Python 專案。

Pipenv

Pipenv 是由一位蠻有名的 Python 套件開發者 Kenneth Reitz 發起,而現在是 PyPA 所管理的一個專案。PyPA 同時也維護了許多 Python 生態系裡面與套件安裝或打包有關的工具。

比起 pip-tools, Pipenv 提供了更完整的環境管理功能。它會替你創建以及管理虛擬環境,所以使用者不再需要考慮怎麼命名或者環境需要擺到哪裡。不同於傳統的 requirements.txt , Pipenv 會用 Pipfile 來擺我們直接用倒的套件然後用 Pipfile.lock 放所有底層的套件及版本。

用法

假設你已經安裝好 Pipenv。用法上可以說是十分直觀:

# 安裝我們所需的套件
pipenv install django "celery<5"
# 移除套件
pipenv uninstall celery
# Activate 虛擬環境
pipenv shell
# 或者也可以用 run 一次性的指令
pipenv run python foobar.py

Poetry

跟 Pipenv 有點像,Poetry 的作者 Sebastien Eustace 也開發過一些有名的 Python 套件,像是Pendulum 。 他開發 Poetry 主要的動機就是希望只用一個工具來管理整個專案環境。一個主要與 Pipenv 的差異就是 Poetry 還支援套件打包以及發布至 PyPI。

用法

在用法上 Poetry 跟 Pipenv 相當類似,你會先需要裝 Poetry 到你的電腦裡 (官方文件),才能開始使用。有一點不同的是,Poetry 有專案的概念,所以第一步需要創建專案的資料夾:

# 假設我們先創建名為 foobar 的專案
# 這個指令會創建一個 foobar 的資料夾以及專案所需的一些檔案
poetry new foobar
# 如果你已經是用在舊專案上,也可以用 poetry init
poetry init

創建專案之後,你就可以跟執行下面的指令來建立環境。

# 安裝套件
poetry add django "celery<5"
# 移除套件
poetry remove celery
# Activate 虛擬環境
poetry shell
# 或者也可以用 run 一次性的指令
poetry run python foobar.py

我應該用 Pipenv 還是 Poetry ?

就像上面所說,一個 Poetry 有而 Pipenv 沒有的功能就是套件打包。Pipenv 在官方文件中有解釋他們比較專注在 application 的環境管理上。所以你如果 library 的開發者而又想只用一個工具的話,Poetry 相較下比較適合。

另外一些差異則跟 dependency resolution 有關。就以 dependency specifcation 來說,Pipenv 是用PEP 440的標準,而 Poetry 則是與 npm 以及 Cargo一樣,用了 semantic versioning。而它們在 resolver 上也都有自己的實作方式 ( 新版的 Pipenv 開始用 pip 的 resolver),所以同樣的輸入是有可能有不同的解析結果。

conda-lock

我自己的工作上常用到資料分析或者是機器學習的套件,而這些套件往往不是純 Python 的,所以可能會需要額外的系統套件或者編譯器支援。conda 是一個非常熱門用來解決這個問題的工具,不過 conda 跟 pip 一樣,也沒有原生支援 lock file 這種概念。需要的讀者可以考慮 conda-lock

在功能上我覺得 conda-lock 有點像 conda 上的 pip-tools。使用者需要提供一個 environment.yml 然後可以把它編譯成不同的平台的 lock files。例子如下:

# 產生 MacOS 以及 Linux 平台的 lock files
# 預設會創建 conda-osx-64.lock 跟 conda-linux-64.lock 兩個檔案
conda-lock -f environment.yml -p osx-64 -p linux-64
# 根據 lock file 的內容去創建一個名為 foobar 的環境
conda-lock install -p foobar conda-linux-64.lock  # default file name
# 也可以透過 conda 的指令來創建環境
conda create -n foobar --file conda-linux-64.lock

conda-lock 也支援一些其它的輸入的格式。包括 conda-build 用的 meta.yaml、flit、以及 Poetry 所用的 pyproject.toml 。不過要注意 conda-lock 功能其實是偏少,譬如說如果你改變的 lock file,想要同步環境,也只能刪除環境然後重新創建新環境。另外一個需要注意的點是現在並不支援 PyPI 上的套件,所以需要的套件沒有在 conda 上,會需要自己跑一次 pip install 來安裝。但這也有弄壞環境的可能性。

總結

在套件管理上,Python 沒有一個官方工具,所以會一直看到有人做新工具在改善現有工具的不足。 所以當你要幫需要長期維護的專案挑工具時,最好還是仔細讀一下文件,因為偶爾會有些你預期外的行為。我在 2021 年的現在對套件管理的選擇是:

  • 當我需要 conda 會用 conda-lock
  • 當專案不大會用 pip-tools
  • 剩下就是用 Poetry

如果有時間可能會介紹幾個看到的新工具,也歡迎給我任何回饋!

Comments

comments powered by Disqus

Published

Category

Blogging

Tags

Contact