Poetry + pyenv 實戰心得:常用指令與注意事項
文章目錄
from Pixabay
〈再見了 pip!最佳 Python 套件管理器——Poetry 完全入門指南〉發表至今,已過了 1 年多,這意味我也用了一年多的 Poetry。
感覺如何?——我覺得還不錯!可見不是三分鐘熱度而已。
對現在的我而言,Poetry 已成為專案開發不可或缺的元素。它不僅提供了更加便捷的專案套件管理和版本控制,同時,Poetry 支援 pyproject.toml 作為設定檔的特性,也使得我更容易使用其他也採用 pyproject.toml 的工具。
系列:Python Poetry 三部曲
- 再見了 pip!最佳 Python 套件管理器——Poetry 完全入門指南
- Poetry + pyenv 實戰心得:常用指令與注意事項
- 終結 requirements.txt:Dockerfile 多階段建構 Poetry 虛擬環境(待發表)
本文主旨
本文將補充系列第一篇中在「情境使用」方面的不足之處,尤其是針對 Poetry 和 pyenv 同時使用時可能出現的問題進行討論。這是第一篇所遺漏的內容。
透過本文,我希望能夠提供更全面、更實用的 Poetry 使用建議,讓讀者在使用 Poetry 和 pyenv 時能夠充分發揮它們的優勢,並減少不必要的困惑。
以 poetry-demo 為例
poetry-demo 是本文作為例示的專案模版,但我們不會安裝太多套件,僅各取一個作為示範之用。
有一個簡單的具體實例,還是比抽象的描述容易理解得多。
我們會從一台全新的 Linux VM(Ubuntu 20.04)開始,先安裝 pyenv,再安裝 Poetry,然後再一起使用它們,建立 Python 專案與虛擬環境。
使用的 Poetry 版本:1.5.1
Poetry 在 1.2 版後,對於部分指令進行有較大的改動與擴張,導致舊指令使用上不完全相容,這次會採用當前的最新版——1.5.1——來進行示範。
使用的 pyenv 版本:2.3.18
pyenv 在 v2.3.0 以後,已經大幅簡化了設定操作,所以這裡也有必要強調一下使用的版本:v2.3.18。
相關文章也有就新版設定內容進行更新,可參考〈Ubuntu 安裝使用 pyenv + pyenv-virtualenv〉。
安裝 Poetry、pyenv、Python 3.10
請直接參考〈Linux Python 開發環境設定:zsh、zinit、pyenv、poetry、docker〉中的「三、設定 pyenv」、「四、設定 Poetry」部分。
不只是安裝,還包括設定 PATH 等環節,這些步驟都是必要的。完成這些步驟後,我們就擁有了 Poetry 和 pyenv。
透過 pyenv 安裝 Python 3.10.11
1 | pyenv install 3.10.11 |
pyenv 是為了方便我們管理多個 Python 版本,下面我們會探討不同專案分別使用多個 Python 版本時的 Poetry + pyenv 操作注意事項。
這裡至少要先有一個 Python 版本,才能順利安裝我們的專案,在此以 Python 3.10.11 為例。
Poetry 與 pyenv 的部分功能重疊
安裝完後,是否要設定pyenv local 3.10.11
或pyenv global 3.10.11
,取決於你是否有「多專案且多種 Python 版本」需求。
如果只需要一種 Python 版本,那將其設定為global
已足:
1 | pyenv global 3.10.11 |
如前述文章所言:
因為 Poetry 自帶了虛擬環境管理功能,容易和 pyenv-virtualenv 疊床架屋,徒增管理上的混淆,所以我現在一律只使用 Poetry + venv 來管理 Python 虛擬環境。
即使在不同專案需要多版本 Python 情況下,pyenv-virtualenv 也不是必須。只要善用pyenv local
和poetry env use
兩大指令即可。
綜上所述,這也是為什麼我認為 Poetry 的教學應該涵蓋對 pyenv 的整合,因為在「虛擬環境管理」方面,兩者的功能有一定重疊。
前置作業總算大功告成,我們開始建立 poetry-demo 吧!
一、初始化 Poetry 專案
先確認一下當前的 Poetry 版本,使用poetry --version
:
1 | ❯ poetry --version |
我的 Poetry 是一段時間前安裝的 1.4.2,需要更新一下,以符合本文使用的版本:
1 | ❯ poetry self update |
因為是使用全域安裝 Poetry,上面的套件更新訊息和專案的虛擬環境無關——畢竟我們根本都還沒有為專案建立專屬的 Python 虛擬環境!
也可以指定要升級的版本:
1 | poetry self update 1.5.1 |
使用poetry init
初始化專案
確認完 Poetry 版本,開始建立專案:
1 | mkdir poetry-demo |
poetry init
會出現下列互動式訊息:
1 | This command will guide you through creating your pyproject.toml config. |
中間的「Would you like to define your main/development dependencies interactively?」兩個問句,我都回答「no」,最後一個則是「yes」。
初始化後的pyproject.toml
內容:
1 | [tool.poetry] |
可以看到其中有一行「readme = "README.md"
」,此時專案必須有README.md
,否則會找不到檔案。我都直接刪除該行,比較省事。
使用poetry new
快速初始化
要達到專案初始化的效果,你也可以直接用poetry new poetry-demo
指令,更快速!不過它也會幫你做「更多事」,細節請參考文件。
二、為專案建立 Python 虛擬環境
這裡是「重頭戲」之一,但做法上並不是那麼「直觀」,常常容易讓人混淆。僅使用 pyenv 的前提下,再安裝pyenv-virtualenv
來建立虛擬環境,確實不難。
但現在有了 Poetry,兩者的搭配使用方式就很重要,這也是為什麼我一再強調,有了 Poetry,乾脆就不要再裝pyenv-virtualenv
了。
使用 Poetry 建立虛擬環境
在第一篇文章中,雖然我提過poetry shell
有時候可以替代poetry env use
,作為快速建立虛擬環境的便捷手段。
但是,當你還沒有為專案建立虛擬環境,且作業系統中包含了不止一個 Python 版本時,建議就不要用poetry shell
來建立虛擬環境——因為它很可能會選擇不是你要的 Python 版本。
儘管我們使用 pyenv 來管理 Python,但完整的 Linux 發行版往往都自帶了系統的 Python。比如我的 Ubuntu 就自帶了 3.8.x,這正是為何上面pyproject.toml
會有一行「python = "^3.8"
」而不是^3.10
——因為 Poetry 偵測到的是系統預設的 Python,而不是 pyenv 的 Poetry。
換句話說,無論透過 pyenv 安裝了幾個 Python 版本,這些資訊對 Poetry 而言,仍可能是陌生的。
為了讓 Poetry 在建立虛擬環境時,能確實使用你想要的 Python 版本,我們必須善用poetry env use
指令才行。
三、確定專案使用的 Python 版本
第一篇文章中也提到:
我覺得學習 Poetry 的第一道關卡,就是它對於虛擬環境的管理。
現在看來一點也沒錯!
指定虛擬環境 Python 版本的標準做法
在使用 pyenv 的情況下,Poetry 官方文件有補充一個讓你能「確定」虛擬環境會使用的 Python 版本的做法:
If you use a tool like pyenv to manage different Python versions, you can set the experimental
virtualenvs.prefer-active-python
option totrue
. Poetry will then try to find the currentpython
of your shell.
其中的「experimental」表示這是一個實驗性功能。所以我不偏好這個做法。
For instance, if your project requires a newer Python than is available with your system, a standard workflow would be:
1 | pyenv install 3.9.8 |
主要分成兩個步驟:
- 將
virtualenvs.prefer-active-python
設為true
。 - 使用 pyenv 的
pyenv local
指令。
我偏好的做法:poetry env use
如果你只確定需要「一個」Python 版本,且已經將其設定為global
,那麼前述的virtualenvs.prefer-active-python
設定,或可省略。
poetry env use
有下列幾種用法:
1 | poetry env use /full/path/to/python |
1 | poetry env use python |
1 | poetry env use python3.7 |
1 | poetry env use 3.7 |
後三者的python
、python3.7
或3.7
,都和你的PATH
有關。
換句話說,如果你在終端機打python3.7
,有成功進入「Python 互動式視窗」,那就表示這個版本的 Python 確實存在PATH
中。
使用which
指令確認 Python 版本是否存在PATH
中
不想進入 REPL,只想確認 Python 版本是否存在PATH
中,可以使用which
指令:
1 | ❯ which python3.9 |
聰明的你應該猜到了,我們只要確保 Python 版本已存在於PATH
,透過poetry env use <指定的python版本>
即可確定專案使用的 Python 版本。
不過,這個<指定的python版本>
必須要先透過 pyenv 安裝好,而且你通常要將其設定為global
或local
,系統才找得到。
poetry-demo 操作
回到案例,這裡我們已經將 3.10.11 設為global
。所以輸入python3.10
指令時,會進入互動式視窗。
此時只要使用下列指令,基本上可以確定使用的 Python 版本:
1 | poetry env use 3.10.11 |
因為已經有設定global
,單純使用poetry env use python
應該也是可以成功套用 3.10。但保險起見,使用指令時還是建議輸入完整的版本號,包括尾綴的.11
四、不同專案使用不同 Python 版本
即使有「多專案多 Python 版本」需求,也未必要變更前述的virtualenvs.prefer-active-python
設定。
透過pyenv local
+poetry env use
,可以為不同專案設定不同的 Python 版本。
假設你有 a、b、c 三個專案,分別要使用 Python 3.7.11、3.9.12、3.10.11,依照前段介紹,我們可以這麼做。
首先,pyenv versions
確認這三個版本的 Python 都已經由 pyenv 安裝完成:
1 | ❯ pyenv versions |
接下來就很簡單了,為各專案設定好pyenv local
(好讓PATH
可以成功找到對應的 Python 執行檔),然後再poetry env use <指定的python版本>
。
假設 b 專案要使用 3.9.12,則做法如下:
1 | cd b |
其餘專案以此類推。
五、如何移除 Poetry 虛擬環境?
參考文件,標準做法如下:
1 | poetry env remove /full/path/to/python |
然而,因為我們已經將virtualenvs.in-project
改設為true
,也就是直接在專案中建立名為.venv
的虛擬環境。
上述的指令基本都沒有作用了。
但我就真的需要砍掉重練啊!怎麼辦?
兩個方法
此時還有兩個簡單的方法可用。
方法一,就是直接砍掉.venv
,簡單有效!
1 | rm -rf .venv |
方法二,我們依舊可以使用下列指令,優雅地移除它:
1 | ❯ poetry env remove --all |
光專案初始化與虛擬環境管理就用掉了 5 個 h2 標題,可見其複雜。現在,我們進入第二部分——套件的安裝與管理。
六、安裝套件至 main dependencies
使用poetry add
指令。
參考文件,可以發現add
指令的用法還挺多元的!
我覺得對一般使用者而言,poetry add
有兩個重點:
- 了解
poetry add
的「多階段行為」。 - 了解
--group
參數用法。
重點一:poetry add
多階段行為
如上篇文章所言,poetry add
實際上會做 3 件事,依序為:
- 更新
pyproject.toml
。 - 依照
pyproject.toml
的內容,更新poetry.lock
。(相當於poetry lock
) - 依照
poetry.lock
的內容,更新虛擬環境。(相當於poetry install
)
為什麼知道這個很重要?
因為當你不是使用poetry add
指令,而是直接修改pyproject.toml
時,此時上述的第 2、3 步都不會自動執行。
但通常你手動修改 toml 檔最終都是為了變更虛擬環境,所以更新完pyproject.toml
後,我們還要再使用poetry lock
與poetry install
指令才行!
對於不熟悉上述流程的初學者,很容易遺漏,並感到困惑。
重點二:--group
舊版(1.1.x)只有 main 和 dev 兩種依賴環境設定,新版(1.2.0)增加了--group
參數,讓你可以除了 main 和 dev 外,還有能自訂多種 group,增加使用上的彈性。
比如可以命名不同的群組如下:
- test
- dev
- prod
基本語法(後續還會提及):
1 | poetry add pytest --group dev |
在新版(1.2)的pyproject.toml
中會如此記載:
1 | [tool.poetry.group.dev.dependencies] |
而舊版則是:
1 | # Poetry pre-1.2.x style, understood by Poetry 1.0–1.2 |
兩者的差異,是版本過渡時要特別注意的。
雖然彈性變大,但我個人目前還是只有使用 main 和 dev 而已。
poetry-demo 操作
至此,我們也安裝 Django 3.2.x 至 main dependencies 中:
1 | ❯ poetry add django@^3.2 |
這個@
符號(運算子)要怎麼用,請參考文件。
七、安裝套件至 dev dependencies
上篇文章中,我們已經探討過「明確區分開發環境專用的套件」的重要性。
舊版的指令是這樣的,以black
為例:
1 | poetry add black --dev |
然而--dev (-D)
在新版已棄用:
•
--dev (-D)
: Add package as development dependency. (Deprecated, use-G dev
instead)
因為加入了 group 機制,新版的指令略有不同:
1 | poetry add black --group dev |
講白了就是變囉嗦了一點。
此時的 toml 檔內容如下:
1 | [tool.poetry] |
八、poetry install --sync
不久前才發現,虛擬環境用久了,安裝的套件似乎和 lock 檔不完全一致!我一直以為兩者是一定同步的🐸,顯然不是。
參考文件,可用下列指令確保同步:
1 | poetry install --sync |
九、Docker 環境中使用 Poetry
前文中有這麼一段,闡述我不在 Docker 中使用 Poetry 的理由與替代方案:
所幸 Poetry 依舊可以輸出
requirements.txt
,Docker 部署環境就繼續使用這個舊方案即可,而且 Poetry 本來主要就是用於「開發」時的套件管理,對部署差別不大。
說是這樣說,但一年多用下來,我發現這個做法也不盡理想,它至少存在兩個問題:
- 套件有變動時,常常會忘記匯出
requirements.txt
:你可以說這是人的問題,但這個 exportrequirements.txt
做法,就真的很容易讓人忘記。 - 由 Poetry 匯出的
requirements.txt
,不一定能透過 pip 正常安裝套件——兩者存在輕微的相容性問題。
怎麼解?我在原文也已經補充了:
使用 multi-stage builds 的 Dockerfile,可以在第一階段安裝 Poetry,第二階段再把 Poetry 捨棄,這樣就不會有多餘的耦合與依賴了。日後會專文介紹。
對,所以系列的第三篇會把這部分補完。
相關文章