from Pixabayfrom Pixabay

2024/04/26:重新編輯全文,提升文字清晰與流暢度。
2024/01/09:刪除部分內容,使文章更緊湊、好讀。

Python 套件管理器——Poetry 完全入門指南〉發表至今,已過了 2 年,這意味著我使用 Poetry 達 2 年了。

對我而言,Poetry 已是 Python 專案開發不可或缺的要素。

它不僅提供了更加便捷的套件管理版本控制。而且,Poetry 以 pyproject.toml 作為設定檔,讓我可以同時透過 pyproject.toml 管理其它工具,比如 Mypy、Ruff 等。

相關文章:Python 開發:pyproject.toml 介紹 + 使用教學

這是我喜歡的方式。

系列:Python Poetry 三部曲

  1. Python 套件管理器——Poetry 完全入門指南
  2. Poetry + pyenv 教學:常用指令與注意事項
  3. Docker 教學:用 Multi-stage build 建立 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

請直接參考〈Linux 上的 Python 開發環境設定〉中的「三、設定 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.11pyenv 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 localpoetry env use 兩大指令即可。

綜上所述,這也是為什麼我認為 Poetry 的教學應該涵蓋對 pyenv 的整合,因為在「虛擬環境管理」方面,兩者的功能有一定重疊。

退萬步言,當你的 Python 專案愈來愈多的時候,你就會深深感受到「一個專案對應一個虛擬環境」(而且最好直接放在專案裡)的必要性

小結:最佳實踐

所以,不要麻煩了!

直接使用 Poetry 的虛擬環境管理功能,並將 virtualenvs.in-project 設為 true,就是我認為的最佳實踐

什麼,你還沒打開?沒關係,給你指令:

1
poetry config virtualenvs.in-project true

前置作業總算大功告成,我們開始建立 poetry-demo 吧!

一、初始化 Poetry 專案

先確認一下當前的 Poetry 版本,使用 poetry --version

1
2
❯ poetry --version
Poetry (version 1.4.2)

我的 Poetry 是一段時間前安裝的 1.4.2,需要升級,以符合本文使用的版本:

1
poetry self update

你也可以指定想要升級的版本:

1
poetry self update 1.5.1

使用 poetry init 初始化專案

確認完 Poetry 版本,開始建立專案:

1
2
3
mkdir poetry-demo
cd poetry-demo
poetry init

poetry init 後會出現互動式訊息,上一篇已有詳細介紹

初始化後的 pyproject.toml 內容:

1
2
3
4
5
6
7
8
9
10
11
12
13
[tool.poetry]
name = "poetry-demo"
version = "0.1.0"
description = ""
authors = ["kyo <odinxp@gmail.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.8"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

比起一開始使用的 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 to true. Poetry will then try to find the current python of your shell.

「experimental」表示這是一個實驗性功能,我不偏好這個做法,在此省略。有興趣的讀者可以自行參考文件。

我偏好的做法:poetry env use

將想用的 Python 版本設定為 globallocal 後,使用 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

後三者的 pythonpython3.73.7,都和你的 PATH 有關。

換句話說,如果你在終端機打 python3.7,有成功進入「Python 互動式視窗」,那就表示這個版本的 Python 確實存在 PATH 中。

使用 which 指令確認 Python 版本是否存在 PATH

不想進入 REPL,只想確認 Python 版本是否存在 PATH 中,可以使用 which 指令:

1
2
3
4
5
6
which python3.9
/home/kyo/.pyenv/shims/python3.9
which python3.10
/home/kyo/.pyenv/shims/python3.10
which python
/home/kyo/.pyenv/shims/python

聰明的你應該猜到了,我們只要確保 Python 版本已存在於 PATH,透過 poetry env use <指定的python版本> 即可確定專案使用的 Python 版本。

不過,這個 <指定的python版本> 必須要先透過 pyenv 安裝好,而且你通常要將其設定為 globallocal,系統才找得到。

poetry-demo 操作

回到案例,這裡我們已經將 3.10.11 設為 global。所以輸入 python3.10 指令時,會進入互動式視窗。

1
2
3
Python 3.10.11 (main, Apr  7 2023, 16:41:32) [Clang 14.0.3 (clang-1403.0.22.14.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>

此時只要使用下列指令建立 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
2
3
4
5
❯ pyenv versions
system
3.7.11
3.9.12
* 3.10.11 (set by /home/kyo/.pyenv/version)

接下來就很簡單了,為各專案設定好 pyenv local(好讓 PATH 可以成功找到對應的 Python 執行檔),然後再 poetry env use <指定的python版本>

假設 b 專案要使用 3.9.12,則做法如下:

1
2
3
cd b
pyenv local 3.9.12
poetry env use 3.9.12

其餘專案以此類推。

五、如何移除 Poetry 虛擬環境?

參考文件,標準做法如下:

1
2
3
4
poetry env remove /full/path/to/python
poetry env remove python3.7
poetry env remove 3.7
poetry env remove test-O3eWbxRl-py3.7

然而,因為我們已經將 virtualenvs.in-project 改設為 true,也就是直接在專案中建立名為 .venv 的虛擬環境。

上述的指令基本都沒有作用了。

但我就真的需要砍掉重練啊!怎麼辦?

兩個方法

此時還有兩個簡單的方法可用。

方法一:直接砍掉 .venv

簡單有效!我都是用這招:

1
rm -rf .venv

此時我們再次體會到,「虛擬環境就是一個資料夾」的真理,以及把虛擬環境放在專案目錄下的好處。

方法二:使用 poetry env remove --all

使用下列指令,優雅地移除它:

1
2
❯ poetry env remove --all
Deleted virtualenv: /home/kyo/poetry-demo/.venv

光是專案初始化虛擬環境管理就用掉大半篇幅,可見其複雜。現在我們進入第二部分——套件的安裝與管理。

六、安裝套件至 main dependencies

使用 poetry add 指令。

參考文件,可以發現 add 指令的「版本範圍條件」寫法還挺多元的!這部分可以參考前一篇後來更新的「指定套件版本範圍」。

除此之外,我覺得對一般使用者而言,poetry add 還有兩個重點

  1. 了解 poetry add 的「多階段行為」。
  2. 了解 --group 參數用法。

重點一:poetry add 多階段行為

如前文所言,poetry add 實際上會做這 3 件事,依序為:

  1. 更新 pyproject.toml
  2. 依照 pyproject.toml 的內容,更新 poetry.lock——相當於執行 poetry lock 指令。
  3. 依照 poetry.lock 的內容,更新虛擬環境——相當於 poetry install 指令。

為什麼知道這個很重要?

因為當你不是使用 poetry add 指令,而是直接修改 pyproject.toml 時,此時上述的第 2、3 步都不會自動執行

但你手動修改 toml 檔,最終就是為了變更虛擬環境,所以在更新完 pyproject.toml 後,我們還要再使用 poetry lockpoetry 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
2
3
[tool.poetry.group.dev.dependencies]
pytest = "^6.0.0"
pytest-mock = "*"

而舊版則是:

1
2
3
4
# Poetry pre-1.2.x style, understood by Poetry 1.0–1.2
[tool.poetry.dev-dependencies]
pytest = "^6.0.0"
pytest-mock = "*"

兩者的差異,是版本過渡時要特別注意的。

雖然彈性變大,但我個人目前還是只有使用 main 和 dev 而已,比如:

1
poetry add black --group dev

poetry-demo 操作

至此,我們來安裝 Django 3.2.x 至 main dependencies 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
❯ poetry add django@^3.2

Updating dependencies
Resolving dependencies... (0.5s)

Package operations: 5 installs, 0 updates, 0 removals

• Installing typing-extensions (4.6.3)
• Installing asgiref (3.7.2)
• Installing pytz (2023.3)
• Installing sqlparse (0.4.4)
• Installing django (3.2.19)

Writing lock file

七、安裝套件至 dev dependencies

上篇文章中,我們已經探討過「明確區分開發環境專用的套件」的重要性。

2025/06/05 更新:在 Poetry 2 以上,以下 4 種寫法皆可,以 black 為例:

1
2
3
4
5
6
7
poetry add black --dev
# 或
poetry add black -D
# 或
poetry add black --group dev
# 或
poetry add black -G dev

這些指令都會將 black 安裝至 dev dependencies 中。


八、poetry install --sync

不久前才發現,虛擬環境用久了,安裝的套件似乎和 lock 檔不完全一致!我一直以為兩者是一定同步的🐸,顯然不是。

參考文件,可用下列指令確保同步:

1
poetry install --sync

九、Docker 環境中使用 Poetry

前文中有這麼一段,闡述我不在 Docker 中使用 Poetry 的理由替代方案

所幸 Poetry 依舊可以輸出 requirements.txt,Docker 部署環境就繼續使用這個舊方案即可,而且 Poetry 本來主要就是用於「開發」時的套件管理,對部署差別不大。

說是這樣說,但一年多用下來,我發現這個做法也不盡理想,它至少存在兩個問題

  1. 套件有變動時,常常會忘記匯出 requirements.txt。你可以說這是人的問題,但這個 export requirements.txt 做法,就真的很容易讓人忘記🤣
  2. 由 Poetry 匯出的 requirements.txt,不一定能透過 pip 正常安裝套件——兩者存在輕微的相容性問題

而系列的第三篇——《Docker 教學:用 Multi-stage build 建立 Poetry 虛擬環境》就是針對這兩個問題,提供一個更好的解決方案