Poetry + pyenv 教學:常用指令與注意事項
文章目錄
from Pixabay
2024/04/26
:重新編輯全文,提升文字清晰與流暢度。2024/01/09
:刪除部分內容,使文章更緊湊、好讀。
〈Python 套件管理器——Poetry 完全入門指南〉發表至今,已過了 2 年,這意味著我使用 Poetry 達 2 年了。
對我而言,Poetry 已是 Python 專案開發不可或缺的要素。
它不僅提供了更加便捷的套件管理和版本控制。而且,Poetry 以 pyproject.toml 作為設定檔,讓我可以同時透過 pyproject.toml 管理其它工具,比如 Mypy、Ruff 等。
這是我喜歡的方式。
系列:Python Poetry 三部曲
- Python 套件管理器——Poetry 完全入門指南
- Poetry + pyenv 教學:常用指令與注意事項
- Docker 教學:Dockerfile 多階段建構 Poetry 虛擬環境(待發表)
本文主旨
本文將補充第一篇中「情境與使用」方面的不足,尤其針對同時使用 Poetry 和 pyenv可能出現的問題進行討論。這是第一篇所遺漏的內容。
透過本文,我希望能夠提供更全面、更實用的 Poetry 使用建議,讓讀者在使用 Poetry 和 pyenv 時能夠充分發揮它們的優勢,並減少不必要的困惑。
範例與環境介紹
poetry-demo 是本文作為例示的專案模版,但我們不會安裝太多套件,僅各取一個作為示範之用。
有一個簡單的具體實例,比單純描述要容易理解。
我們會從一台全新的 Linux VM(Ubuntu 20.04)開始,安裝 pyenv,再安裝 Poetry,最後再一起使用它們,建立 Python 專案與虛擬環境。
Poetry 版本:1.5.1
Poetry 在 1.2 版後,對於部分指令進行有較大的改動,導致舊有的指令不完全相容,這次會採用當前(2023 年 6 月)的最新版——1.5.1——來進行示範。
pyenv 版本:2.3.18
pyenv 在 v2.3.0 以後,已經大幅簡化了設定操作。本文使用版本:v2.3.18。
相關文章也有就新版設定內容進行更新,可參考:
安裝 Poetry、pyenv
請直接參考〈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 版本,才能順利安裝我們的專案,在此以 3.10.11 為例。
Poetry 與 pyenv 的整合
安裝完後,是否要設定pyenv local 3.10.11
或pyenv global 3.10.11
,取決於你是否有「多專案、多 Python 版本」需求——通常一定會有☺️
設定 pyenv global 與 local
如果只需要一種 Python 版本,那將其設定為global
已足:
1 | pyenv global 3.10.11 |
但你也可以同時設定兩者,這樣在特定專案中會使用特定 Python 版本,而在不指定時則使用global
設定的 Python 版本。
2024/06/21
補充:Poetry 有時就是不鳥你的pyenv global
設定,而直接使用作業系統的預設 Python 版本。所以最好還是用pyenv local
。
比如幫某個舊專案設定為 3.8.12:(要先cd
至該專案目錄)
1 | pyenv local 3.8.12 |
此時專案中會新增一個.python-version
檔案,內容就是你設定的 Python 版本:
1 | 3.8.12 |
建議:不要使用 pyenv-virtualenv
如前文所言:
因為 Poetry 自帶了虛擬環境管理功能,容易和 pyenv-virtualenv 疊床架屋,徒增管理上的混淆,所以我現在一律只用 Poetry + venv 來管理 Python 虛擬環境。
即使在不同專案需要多版本 Python 情況下,pyenv-virtualenv 也不是必須。只要善用pyenv local
和poetry env use
兩大指令即可。
綜上所述,這也是為什麼我認為 Poetry 的教學應該涵蓋對 pyenv 的整合,因為在「虛擬環境管理」方面,兩者的功能有一定重疊。
退萬步言,當你的 Python 專案愈來愈多的時候,你就會深深感受到「一個專案只對應一個虛擬環境」(而且最好直接放在專案裡)的必要性。
小結:最佳實踐
所以,不要麻煩了!
直接使用 Poetry 的虛擬環境管理功能,並將virtualenvs.in-project
設為true
,就是我認為的最佳實踐。
什麼,你還沒打開?沒關係,給你指令:
1 | poetry config virtualenvs.in-project true |
前置作業總算大功告成,我們開始建立 poetry-demo 吧!
一、初始化 Poetry 專案
先確認一下當前的 Poetry 版本,使用poetry --version
:
1 | ❯ poetry --version |
我的 Poetry 是一段時間前安裝的 1.4.2,需要升級,以符合本文使用的版本:
1 | poetry self update |
你也可以指定想要升級的版本:
1 | poetry self update 1.5.1 |
使用poetry init
初始化專案
確認完 Poetry 版本,開始建立專案:
1 | mkdir poetry-demo |
poetry init
後會出現互動式訊息,上一篇已有詳細介紹。
初始化後的pyproject.toml
內容:
1 | [tool.poetry] |
比起一開始使用的 v1.19,新版多了這行:
1 | readme = "README.md" |
我習慣——直接刪除。
因為沒有README.md
,反而會造成錯誤。如果有就可以保留。
二、為專案建立 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 版本
在有多個 Python 版本的情況下,想讓 Poetry 使用特定的 Python 版本,有時真的不是那麼容易😂
指定虛擬環境 Python 版本的官方做法(experimental)
在使用 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」表示這是一個實驗性功能,我不偏好這個做法,在此省略。有興趣的讀者可以自行參考文件。
我偏好的做法:poetry env use
將想用的 Python 版本設定為global
或local
後,使用poetry env use
指令來確保專案虛擬環境的 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
指令時,會進入互動式視窗。
1 | Python 3.10.11 (main, Apr 7 2023, 16:41:32) [Clang 14.0.3 (clang-1403.0.22.14.1)] on darwin |
此時只要使用下列指令建立 Poetry 虛擬環境,基本上可以確定,使用的 Python 版本為 3.10.11:
1 | poetry env use 3.10.11 |
保險起見,使用指令時還是建議輸入完整的版本號,例如3.10.11
,而不是3.10
。尤其是當你的 pyenv 中有多個 3.10.x 版本時,這樣可以避免混淆。
四、不同專案使用不同 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 |
此時我們再次體會到,「虛擬環境就是一個資料夾」的真理,以及把虛擬環境放在專案目錄下的好處。
方法二:使用poetry env remove --all
使用下列指令,優雅地移除它:
1 | ❯ poetry env remove --all |
光是專案初始化與虛擬環境管理就用掉大半篇幅,可見其複雜。現在我們進入第二部分——套件的安裝與管理。
六、安裝套件至 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 而已,比如:
1 | poetry add black --group 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 捨棄,這樣就不會有多餘的耦合與依賴了。日後會專文介紹。
對,所以系列的第三篇會把這部分補完。
相關文章