Files
Obsidian-Main/20.01. Programming/Python/Poetry.md

1030 lines
51 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
來源:[再見了 pip最佳 Python 套件管理器——Poetry 完全入門指南](https://blog.kyomind.tw/python-poetry/?fbclid=IwAR0PsmydXgpKzpbAWzJeK2ze7o0uXgGHn24xUvTCjo9fQQGGhcpnkPBG9n8)
---
# 再見了 pip最佳 Python 套件管理器——Poetry 完全入門指南
[![by Feifei Ruan on Behance](https://i.imgur.com/3QW32TX.jpg)]
前陣子工作上的專案從原先的 pip 改用 Poetry 管理 Python 套件,由於採用 Poetry 正是我的提議,所以得身先士卒,研究 Poetry 使用上的重點與學習成本,並評估是否真有所值——講白了就是至少要利大於弊,不然會徒增團隊適應上的負擔。
拜這個機會所賜,我對 Poetry 總算有了一個較為全面的理解。
習慣後,現在我所有的個人開發也都改用 Poetry 來管理套件及虛擬環境,對於 Poetry 這個略嫌複雜的工具(相較 pip上手的同時我也感受到它確實存在一些**學習門檻**,間接促使了本文的誕生。
### 本文定位:獻給 Poetry 新手的使用說明書
有鑑於 Poetry 真的有點複雜,如果要推薦別人使用,我想還是有必要好好介紹一下。換句話說,這會是一篇**完整的入門教學**。
本文除了講解如何使用 Poetry還會先**不厭其煩地闡述它所解決的痛點**,如果對此興趣有限,可以直接跳到「[從零開始使用 Poetry](https://blog.kyomind.tw/python-poetry/?fbclid=IwAR0PsmydXgpKzpbAWzJeK2ze7o0uXgGHn24xUvTCjo9fQQGGhcpnkPBG9n8#%E5%BE%9E%E9%9B%B6%E9%96%8B%E5%A7%8B%E4%BD%BF%E7%94%A8-Poetry)」章節,但看完前導部分,相信能更加體會 Poetry 的**必要性與強大**之處。
為了讓你**無痛上手**,這將會是一篇超過 8000 字的長文,還請多多擔待。🙏
### 本文目錄
方便快速跳轉到有興趣的部分,桌面版用戶可和右下角的「回到最上方」搭配使用:
1. [Poetry 是什麼?](https://blog.kyomind.tw/python-poetry/?fbclid=IwAR0PsmydXgpKzpbAWzJeK2ze7o0uXgGHn24xUvTCjo9fQQGGhcpnkPBG9n8#Poetry-%E6%98%AF%E4%BB%80%E9%BA%BC%EF%BC%9F)
2. [名詞解釋:虛擬環境管理、套件管理、相依性管理](https://blog.kyomind.tw/python-poetry/?fbclid=IwAR0PsmydXgpKzpbAWzJeK2ze7o0uXgGHn24xUvTCjo9fQQGGhcpnkPBG9n8#%E5%90%8D%E8%A9%9E%E8%A7%A3%E9%87%8B%EF%BC%9A%E8%99%9B%E6%93%AC%E7%92%B0%E5%A2%83%E7%AE%A1%E7%90%86%E3%80%81%E5%A5%97%E4%BB%B6%E7%AE%A1%E7%90%86%E3%80%81%E7%9B%B8%E4%BE%9D%E6%80%A7%E7%AE%A1%E7%90%86)
3. [pip 的最大不足](https://blog.kyomind.tw/python-poetry/?fbclid=IwAR0PsmydXgpKzpbAWzJeK2ze7o0uXgGHn24xUvTCjo9fQQGGhcpnkPBG9n8#pip-%E7%9A%84%E6%9C%80%E5%A4%A7%E4%B8%8D%E8%B6%B3)
4. [pip 替代方案選擇——Pipenv vs Poetry](https://blog.kyomind.tw/python-poetry/?fbclid=IwAR0PsmydXgpKzpbAWzJeK2ze7o0uXgGHn24xUvTCjo9fQQGGhcpnkPBG9n8#pip-%E6%9B%BF%E4%BB%A3%E6%96%B9%E6%A1%88%E9%81%B8%E6%93%87%E2%80%94%E2%80%94Pipenv-vs-Poetry)
5. [選擇 Poetry 的兩個理由](https://blog.kyomind.tw/python-poetry/?fbclid=IwAR0PsmydXgpKzpbAWzJeK2ze7o0uXgGHn24xUvTCjo9fQQGGhcpnkPBG9n8#%E9%81%B8%E6%93%87-Poetry-%E7%9A%84%E5%85%A9%E5%80%8B%E7%90%86%E7%94%B1)
6. [從零開始使用 Poetry](https://blog.kyomind.tw/python-poetry/?fbclid=IwAR0PsmydXgpKzpbAWzJeK2ze7o0uXgGHn24xUvTCjo9fQQGGhcpnkPBG9n8#%E5%BE%9E%E9%9B%B6%E9%96%8B%E5%A7%8B%E4%BD%BF%E7%94%A8-Poetry)
7. [安裝 Poetry](https://blog.kyomind.tw/python-poetry/?fbclid=IwAR0PsmydXgpKzpbAWzJeK2ze7o0uXgGHn24xUvTCjo9fQQGGhcpnkPBG9n8#%E5%AE%89%E8%A3%9D-Poetry)
8. [初始化 Poetry 專案](https://blog.kyomind.tw/python-poetry/?fbclid=IwAR0PsmydXgpKzpbAWzJeK2ze7o0uXgGHn24xUvTCjo9fQQGGhcpnkPBG9n8#%E5%88%9D%E5%A7%8B%E5%8C%96-Poetry-%E5%B0%88%E6%A1%88)
9. [管理 Poetry 虛擬環境](https://blog.kyomind.tw/python-poetry/?fbclid=IwAR0PsmydXgpKzpbAWzJeK2ze7o0uXgGHn24xUvTCjo9fQQGGhcpnkPBG9n8#%E7%AE%A1%E7%90%86-Poetry-%E8%99%9B%E6%93%AC%E7%92%B0%E5%A2%83)
10. [Poetry 常用指令](https://blog.kyomind.tw/python-poetry/?fbclid=IwAR0PsmydXgpKzpbAWzJeK2ze7o0uXgGHn24xUvTCjo9fQQGGhcpnkPBG9n8#Poetry-%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4)
11. [Poetry 常見使用情境與操作 QA](https://blog.kyomind.tw/python-poetry/?fbclid=IwAR0PsmydXgpKzpbAWzJeK2ze7o0uXgGHn24xUvTCjo9fQQGGhcpnkPBG9n8#Poetry-%E5%B8%B8%E8%A6%8B%E4%BD%BF%E7%94%A8%E6%83%85%E5%A2%83%E8%88%87%E6%93%8D%E4%BD%9C-QA)
12. [結語:井然有序的複雜](https://blog.kyomind.tw/python-poetry/?fbclid=IwAR0PsmydXgpKzpbAWzJeK2ze7o0uXgGHn24xUvTCjo9fQQGGhcpnkPBG9n8#%E7%B5%90%E8%AA%9E%EF%BC%9A%E4%BA%95%E7%84%B6%E6%9C%89%E5%BA%8F%E7%9A%84%E8%A4%87%E9%9B%9C)
---
## Poetry 是什麼?
要了解 Poetry 大致的作用與功能,參考 [Poetry GitHub](https://github.com/python-poetry/poetry#poetry-dependency-management-for-python) 說明是一個不錯的開始:
> **Poetry: Dependency Management for Python**
> Poetry helps you declare, manage and install dependencies of Python projects
而 [Poetry 官網](https://python-poetry.org/)的 slogan 則更加簡潔有力:
![python-poetry.org](https://i.imgur.com/xnYx0FB.png)
簡單來說,**Poetry 類似 pip能協助你進行套件管理dependency management但又比 pip 強大得多,因為它還包含了 pip 所未有的下列功能:**
- 虛擬環境管理
- 套件相依性管理
- 套件的打包與發布
其中**最為關鍵**的是「**套件的相依性管理**」,也是本文的重點,而「套件的打包與發布」與主題較無關係,所以不會提及。
## 名詞解釋:虛擬環境管理、套件管理、相依性管理
開始前,要先大致說明標題中這三者的區別,才不易混淆文中的內容。這裡的定義可能不盡準確,但至少對理解文中的表達能有所幫助。
### 虛擬環境管理
指的是使用內建的 venv 或 virtualenv 套件來建立及管理 Python 的虛擬環境,不同的虛擬環境間各自獨立,也就是對應的路徑各不相同。
### 套件管理、依賴管理dependency management
指的是使用 pip 這類的套件管理器來管理 Python 環境未必是虛擬環境即管理環境中所安裝的全部套件package、dependency及其版本。
在這個**語境**下dependency 基本上就是指你安裝的 package。
### 「套件的」相依性管理、依賴解析
這個有點難定義,它並不是一個非常通俗且有共識的名詞,我在英文中也還難找到對應的單字。本文使用它時,主要指的是**套件與套件之間的依賴關係及版本衝突管理**,也就是套件的「**相依性管理**」。在下文提及的 Podcast 中,又稱為「**依賴解析**」。
所謂套件的「**版本衝突**」指的是單一套件被兩個以上的套件所依賴,但不同的套件對依賴的套件有著不同的**最低或最高版本要求**,若兩者要求的範圍「**沒有交集**」,則會發生衝突而導致套件**失效**或**無法安裝**。
## pip 的最大不足
大概在 2 年前就聽過 Poetry 的大名,不過那時我還沒有套件相依性管理的強烈需求,加上看起來需要一些學習成本(確實如此),所以就一直擱在一旁,直到真正體會到了 pip 的不足。
pip 是 Python 內建的套件管理工具,而它的最大罩門,就是對於「套件間的相依性管理」能力不足。尤其是在「**移除**」套件時的依賴解析——可以說沒有。這也是我提議改用 Poetry 的根本原因。
怎麼說?看完下面的例子就能明白。
### `pip uninstall`的困境:以 Flask 為例
假設現在你的工作專案中有開發 API 的需求,經過一番研究與討論,決定使用 [Flask](https://github.com/pallets/flask) 網頁框架來進行開發。
我們知道,很多套件都有依賴的套件,也就是使用「別人已經造好的輪子」來構成套件功能的一部分。
安裝主套件時,這些依賴套件也**必須一併安裝,主套件才能正常運作**,這裡的 Flask 就是如此。安裝 Flask 時,不會只安裝單一個`flask`套件,還會安裝所有 Flask 的必要構成部分——也就是依賴套件,結果如下:
```
pip install flask
Collecting flask
Downloading Flask-2.1.1-py3-none-any.whl (95 kB)
|████████████████████████████████| 95 kB 993 kB/s
Collecting importlib-metadata>=3.6.0
Using cached importlib_metadata-4.11.3-py3-none-any.whl (18 kB)
Collecting itsdangerous>=2.0
Downloading itsdangerous-2.1.2-py3-none-any.whl (15 kB)
Collecting Werkzeug>=2.0
Downloading Werkzeug-2.1.1-py3-none-any.whl (224 kB)
|████████████████████████████████| 224 kB 2.8 MB/s
Collecting click>=8.0
Downloading click-8.1.2-py3-none-any.whl (96 kB)
|████████████████████████████████| 96 kB 1.9 MB/s
Collecting Jinja2>=3.0
Downloading Jinja2-3.1.1-py3-none-any.whl (132 kB)
|████████████████████████████████| 132 kB 3.7 MB/s
Collecting zipp>=0.5
Using cached zipp-3.7.0-py3-none-any.whl (5.3 kB)
Collecting MarkupSafe>=2.0
Downloading MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl (13 kB)
Installing collected packages: zipp, MarkupSafe, Werkzeug, Jinja2, itsdangerous, importlib-metadata, click, flask
Successfully installed Jinja2-3.1.1 MarkupSafe-2.1.1 Werkzeug-2.1.1 click-8.1.2 flask-2.1.1 importlib-metadata-4.11.3 itsdangerous-2.1.2 zipp-3.7.0
```
從上可知,`pip install flask`還會一併安裝`importlib-metadata``itsdangerous`等 7 個依賴套件,實際上總共安裝了 8 個套件!
可以說pip 在「安裝」套件時的相依性管理還是可以的,這並不難,因為套件的依賴要求都寫在安裝檔裡了,根本不需要「管理」。
---
附帶一提,這 8 個套件包括`flask`,除了`importlib-metadata``zipp`外,其餘 6 個實際上都是由 [Flask 團隊自行開發](https://palletsprojects.com/p/)。
但並非只有 Flask 框架會使用(依賴)這些套件。
比如其中的 [Click](https://palletsprojects.com/p/click/) 就是一個被廣泛使用的命令列製作工具。套件官網是這麼介紹的:
> Click is a Python package for **creating beautiful command line interfaces** in a composable way with as little code as necessary.
別的套件也可能依賴`click`來提供命令列的功能,換句話說,主套件的依賴套件也可能被其他第三方套件所依賴、使用。**這就產生了「衝突」的可能。**
---
好,一切都很美好,就這樣一年過去,團隊決定改用火紅的 FastAPI 取代 Flask 來實作專案的 API作為 API 的主要開發人員,你對新技術充滿了期待(或排斥),興高采列地安裝了 FastAPI更新了所有程式碼最後要移除 Flask這時問題就來了。
安裝 Flask 的時候,我們只需要`pip install flask`pip 就會幫你一併安裝所有依賴套件。現在要移除它,也只要`pip uninstall flask`就可以了嗎?
> **很遺憾,答案是否定的**。
### pip 的致命缺陷:缺乏移除套件時的依賴解析(相依性管理)
僅執行`pip uninstall flask`的話pip 就**真的只會**幫你移除`flask`這個套件**本身**而已。那剩下的、再也用不到的套件怎麼辦?你只能一個一個手動移除!
但你千萬不要真的嘗試手動移除依賴套件!——因為你無法確定這些依賴套件**是否同時被別的套件所依賴**。
### pip 手動移除依賴套件的潛在風險:以 Flask + Black 為例
繼續以 Flask 為例,還記得其中一個依賴套件是`click`,如前所述,它是一個協助製作命令列界面的工具。
假設專案中已同時安裝了 [Black](https://github.com/psf/black) 這個 Python code formatter 來統一程式碼排版(沒錯!我現在個人開發也都改用 Black 取代 [yapf](https://github.com/google/yapf) Black 是一個可以透過 CLI 指令執行的格式化工具,剛好,它也是使用`click`來實作命令列功能。
> **Black formatter** 相關文章:
>
> - [試用從 VS Code Python extension 拆分的 Black、isort 套件](https://blog.kyomind.tw/black-and-isort/)
> - [pyproject.toml 介紹 + VS Code 整合 Black、yapf、isort 教學](https://blog.kyomind.tw/pyproject-toml/)
我們可以藉由 Poetry 指令(後續介紹)來查看,目前這唯二安裝套件的**依賴關係樹**
```
black 22.3.0 The uncompromising code formatter.
├── click >=8.0.0
│ └── colorama *
├── mypy-extensions >=0.4.3
├── pathspec >=0.9.0
├── platformdirs >=2
├── tomli >=1.1.0
└── typing-extensions >=3.10.0.0
flask 2.1.2 A simple framework for building complex web applications.
├── click >=8.0
│ └── colorama *
├── importlib-metadata >=3.6.0
│ └── zipp >=0.5
├── itsdangerous >=2.0
├── jinja2 >=3.0
│ └── markupsafe >=2.0
└── werkzeug >=2.0
```
可以明顯看出,**兩者都依賴了`click`套件**。可想而知,移除 Flask 時,如果你同時把`click`也**跟著一併移除**,會發生什麼樣的悲劇——**你的 Black 壞了**——因為它缺少了必要的依賴套件`click`
簡言之,直接 pip 手動移除依賴套件存在下列兩大疑慮,不建議輕易嘗試:
#### 一、無法確定想移除的套件還有多少依賴套件
正常而言,你不會去注意安裝時總共一併安裝了多少依賴套件。雖然有`pip show`這類的指令可以大概知曉套件的依賴,但這指令只會顯示「**直接**依賴套件」而不會顯示「依賴套件的依賴」,所以列出來的結果未必準確:
```bash
pip show flask
Name: Flask
Version: 2.1.1
Summary: A simple framework for building complex web applications.
Home-page: https://palletsprojects.com/p/flask
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
License: BSD-3-Clause
Location: /Users/kyo/.pyenv/versions/3.8.12/envs/test/lib/python3.8/site-packages
Requires: importlib-metadata, Werkzeug, click, Jinja2, itsdangerous
Required-by:****
```
可以看到,`Requires:`只顯示了 5 個依賴套件,因為剩下的 2 個(`zipp``markupsafe`)是「**依賴的依賴**」,在更下層,並未顯示。
#### 二、即使確定所有依賴套件,也無法確定這些套件是否還被其他套件所依賴
好繞口啊!上述的`click`例子就是解釋這個困境。
### 小結pip 只適合小型專案或「只新增不移除」套件的專案
以前我的個人或工作上的專案往往規模不大pip 就真的只負責新增,鮮少需要考慮移除套件的情況,所以缺少移除套件時的依賴解析,似乎也沒什麼大問題。
但稍具模規的專案往往就需要考慮套件的退場,以維持開發及部署環境的簡潔,尤其在使用容器化部署時,**過多不必要的套件會徒增 image 肥大,產生額外的成本與資源浪費不說,同時也提升了「套件之間發生衝突」的可能。**
然而透過上述的例子可知,僅靠 pip 想要**乾淨移除**過時的套件,且不影響既有的套件,簡直是**不可能的任務!**所以我們需要擁有「**完整套件相依性管理功能**」的套件管理器。
---
## pip 替代方案選擇——Pipenv vs Poetry
關於 pip 的前世今生、它的歷史包袱,以及為何它難以演化成理想的、可以完美管理套件相依性的模樣,可以參考〈[告別 Anaconda在 macOS 上使用 pyenv 建立 Python 開發環境](https://blog.kyomind.tw/pyenv-setup/)〉中推薦過的單集 Podcast
- [《捕蛇者說》Ep 15. 和 PyPA 的成員聊聊 Python 開發工作流](https://pythonhunter.org/episodes/ep15)
從 Podcast 官方頁面中「時間節點」目錄中可知,該集對 Python 的虛擬環境與套件管理機制及相關工具,有著非常廣泛的討論,十分精彩,強力推薦!(為了寫這篇又聽了第 3 次)
![pythonhunter.org/episodes/ep15](https://i.imgur.com/gzcAU7e.png)
### Pipenv vs Poetry
因為 pip 存在這樣的致命弱點,所以很早就有相關的方案提出想要解決它,最知名的莫過於 [Pipenv](https://pipenv.pypa.io/en/latest/)
而講到需要有充分「套件相依性管理」功能的套件管理器,你基本上也只能從 Pipenv 和 Poetry 兩者之中二擇一了。
如果是在兩年前,這道選擇題恐怕不容易回答,且 Pipenv 可能會有較大的機率勝出,**但兩年後的今天,我會建議你毫不猶豫地選擇 Poetry。**
## 選擇 Poetry 的兩個理由
怎麼說呢?以下是我的理由。
### 理由一Pipenv 的不足
當你搜尋「python poetry」關鍵字的時候那些教你怎麼使用 Poetry 的文章,往往也會一併提及**為何不選擇 Pipenv。**
以下兩篇有著較為完整的說明,容我直接引用。
〈[Python - 取代 Pipenv 的新套件管理器 Poetry](https://note.koko.guru/posts/using-poetry-manage-python-package-environments)〉:
> Pipenv 雖然強大,卻也暴露出了一些問題如 Lock 過慢、Windows 支援性差、對 PyPI 套件打包的友善度差…等更多其他問題,甚至有越來越多人表明 [不要使用 Pipenv](http://greyli.com/do-not-use-pipenv/) 或 [pipenv 的凋零與替代方案 poetry](https://blog.gslin.org/archives/2019/12/21/9347/pipenv-%E7%9A%84%E5%87%8B%E9%9B%B6%E8%88%87%E6%9B%BF%E4%BB%A3%E6%96%B9%E6%A1%88-poetry/) 等。
> 同時 Pipenv 的社群維護狀況也越來越差,有許多的 PR 都沒有被 Release導致許多貢獻者抱怨甚至有人發出了該篇 [If this project is dead, just tell us](https://github.com/pypa/pipenv/issues/4058) issue 想知道是否專案已經不在維護。
〈[相比 PipenvPoetry 是一個更好的選擇](https://greyli.com/poetry-a-better-choice-than-pipenv/)〉(本文作者[李輝](https://greyli.com/about/)為 Flask 團隊成員):
> Pipenv 描繪了一個美夢,讓我們以為 Python 也有了其他語言那樣完善的包管理器,不過這一切卻在後來者 Poetry 這裡得到了更好的實現。
> 這幾年 Pipenv 收獲了很多用戶,但是也暴露了很多問題。雖然 Lock 太慢、Windows 支持不好和 bug 太多的問題都已經改進了很多,但對我來說,仍然不能接受隨時更新鎖定依賴的設定,在上一篇文章《[不要用 Pipenv](http://greyli.com/do-not-use-pipenv/)》裡也吐槽了很多相關的問題。
兩篇的引述內容總結就是一句話:不要用 Pipenv。
目前 Pipenv 已經由 [PyPA](https://github.com/pypa)(同時也維護 pip 及 virtualenv接手上述「**擺爛**」的情況相信已有所改善,不過我似乎還沒看到有什麼文章大力鼓吹或宣告 Pipenv 已經「great again」所以個人對它的未來發展還是持保留態度。
### 理由二pyproject.toml
pyproject.toml 是 [PEP 518](https://peps.python.org/pep-0518/) 所提出的新標準:
> The build system dependencies will be stored in a file named `pyproject.toml` that is written in the TOML format.
原意是作為套件打包設定的標準格式,後來又有了 [PEP 621](https://peps.python.org/pep-0621/),將其**擴充定性**為 **Python 生態系工具的共同設定檔標準**,現在已經被愈來愈多套件所支援,詳細可參考[這個清單](https://github.com/carlosperate/awesome-pyproject)及頁面中的說明:
> `pyproject.toml` is a new configuration file defined in [PEP 518](https://www.python.org/dev/peps/pep-0518/) and expanded in [PEP 621](https://www.python.org/dev/peps/pep-0621/). It is design to store build system requirements, **but it can also store any tool configuration for your Python project**, possibly replacing the need for `setup.cfg` or other tool-specific files.
作為規範控,我很願意追隨這個標準。
並且Poetry 使用`pyproject.toml`,可遠遠不止是設定檔的程度,更是**不可或缺**的一部分。相當於 Pipenv 的`Pipfile`或 npm 的`package.json`
換句話說,少了`pyproject.toml`Poetry 便無法運作。
> 更多關於 **pyproject.toml** 的介紹與實踐,可參考:[pyproject.toml 介紹 + VS Code 整合 Black、yapf、isort 教學](https://blog.kyomind.tw/pyproject-toml/)
---
好,漫長的前言到此結束,讓我們進入正題,開始上手學習 Poetry。
## 從零開始使用 Poetry
本文所有的參考資料會放在文末的「參考」一欄中,不過在此還是要特別提及主要的參考對象,總共有二:
- [Poetry 官方文件](https://python-poetry.org/docs/)
- [Dependency Management With Python Poetry](https://realpython.com/dependency-management-python-poetry/)
如果在本文找不到你需要的內容,以上二處可能會有,所以主動列出。
另外本文主要以 macOS 和 LinuxUbuntu環境來進行安裝及教學Windows 用戶如果有無法順利安裝的情況,建議參考官方文件內容修正。不過,即使有問題,應該也是集中在安裝與設定階段,本文其餘部分仍可適用。
## 安裝 Poetry
Poetry 和 pip、git、pyenv 等工具一樣,都是典型的**命令列工具**,需要先安裝才能下達指令——`poetry`
### 安裝方式選擇
Poetry 主要提供了[兩種安裝方式](https://python-poetry.org/docs/#installation)
1. **全域安裝**至使用者的家目錄。
2. **pip 安裝**至專案使用的 Python虛擬環境`pip install poetry`
**個人推薦使用全域安裝**,官方文件也表示[不推薦使用 pip 安裝](https://python-poetry.org/docs/#alternative-installation-methods-not-recommended)。
因為 pip 安裝是直接安裝到「**專案所屬的 Python 虛擬環境**」裡,而 Poetry 所依賴的套件非常多,**總計超過 30 個,會嚴重影響專案虛擬環境的整潔度**。文件中也警告這些依賴套件可能和專案本身的套件**發生衝突**
> Be aware that it will also install Poetrys dependencies which **might cause conflicts with other packages.**
### 全域安裝 Poetry 至家目錄
所以我們就使用全域安裝吧!
#### macOS / Linux / WSLWindows Subsystem for Linux
```
curl -sSL https://install.python-poetry.org | python3 -
```
```
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
```
#### Windows
```
(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | python -
```
Poetry 實際安裝路徑如下:
> The installer installs the `poetry` tool to Poetrys `bin` directory. This location depends on your system:
- `$HOME/.local/bin` for Unix
- `%APPDATA%\Python\Scripts` on Windows
以 macOS 為例,如果要下`poetry`指令,就需要打完整路徑`$HOME/.local/bin/poetry`,顯然不太方便,所以我們需要設定 PATH。
### 設定 PATH
新增`poetry`指令執行檔所在的路徑至 PATH。
`.zshrc``.bashrc``.bash_profile`新增:
```
export PATH=$PATH:$HOME/.local/bin
```
存檔後重啟 shell 即可使用。直接在命令列打上`poetry`指令測試:
```
poetry
Poetry version 1.1.13
USAGE
poetry [-h] [-q] [-v [<...>]] [-V] [--ansi] [--no-ansi] [-n] <command> [<arg1>] ... [<argN>]
...
```
### 設定 alias
比起`pip``poetry`這個指令實在太冗長了!我們還是給它一個 alias 吧!
基於它是我極為常用的指令,我願意賦與它**「單字母」的 alias 特權**,我使用`p`
```
alias p='poetry'
```
測試結果:
```
p
Poetry version 1.1.13
USAGE
poetry [-h] [-q] [-v [<...>]] [-V] [--ansi] [--no-ansi] [-n] <command> [<arg1>] ... [<argN>]
```
alias 是方便自己使用,但本文基於表達清晰考量,下面的解說除了圖片外,原則上並不會使用 alias 表示。
---
## 初始化 Poetry 專案
為了方便解說,我們先建立一個全新的專案,名為`poetry-demo`
指令都很簡單,但還是建議可以一步一步跟著操作。
就像 git 專案需要初始化Poetry 也需要,因為每一個使用了 Poetry 的專案中一定要有一個`pyproject.toml`作為它的**設定檔**。否則直接使用`poetry`相關指令就會出現下列錯誤訊息:
> Poetry could not find a pyproject.toml file in {cwd} or its parents
所以一定先初始化,使用`poetry init`
```
mkdir poetry-demo
cd poetry-demo
poetry init
```
此時會跳出一連串的互動對話,協助你建立專案的資料,大部分可以直接`enter`跳過:
```
This command will guide you through creating your pyproject.toml config.
Package name [poetry-demo]:
Version [0.1.0]:
Description []:
Author [kyo <odinxp@gmail.com>, n to skip]:
License []:
Compatible Python versions [^3.8]:
Would you like to define your main dependencies interactively? (yes/no) [yes]
```
直到出現「`Would you like to define your main dependencies interactively? (yes/no) [yes]`」,我們先選擇「**no**」後,會讓你確認本次產生的`toml`檔內容:
```
Would you like to define your development dependencies interactively? (yes/no) [yes] no
Generated file
[tool.poetry]
name = "poetry-demo"
version = "0.1.0"
description = ""
authors = ["kyo <odinxp@gmail.com>"]
[tool.poetry.dependencies]
python = "^3.8"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
```
並詢問你「`Do you confirm generation? (yes/no) [yes]`」,按`enter`使用預設選項「yes」或直接回答「yes」`pyproject.toml`建立完成。
此時專案目錄結構如下:
```
poetry-demo
└── pyproject.toml
0 directories, 1 file
```
## 管理 Poetry 虛擬環境
我覺得學習 Poetry 的**第一道關卡**,就是它對於虛擬環境的管理。
### 「強制」虛擬環境
Poetry 預設上(可透過`poetry config`修改)會強制套件都要安裝在虛擬環境中,以免污染全域,所以它整合了`virtualenv`
所以在執行`poetry add、install`等指令時Poetry 都會自動檢查**當下是否正在使用虛擬環境:**
- 如果**是**,則會直接安裝套件至**當前**的虛擬環境。
- 如果**否**,則會自動幫你建立一個**新的**虛擬環境,再進行套件安裝。
### 容易混淆的虛擬環境
Poetry 主動納入虛擬環境管理算是立意良善,相當於把`pip`+`venv`兩者的功能直接整合在一起,**但也帶來一定的複雜度**,尤其在你已經自行使用了`venv``virtualenv`或 `pyenv-virtualenv``conda`等工具來管理虛擬環境的情況下!
**沒錯Python 的虛擬環境管理就是這麼麻煩!**
個人建議,對新手而言,於 Poetry 的專案中,**一律使用 Poetry** 來管理虛擬環境即可。我目前也是這樣,省得麻煩。
### 以指令建立虛擬環境
使用指令`poetry env use python`
```
poetry env use python
Creating virtualenv poetry-demo-IEWSZKSE-py3.8 in /Users/kyo/Library/Caches/pypoetry/virtualenvs
Using virtualenv: /Users/kyo/Library/Caches/pypoetry/virtualenvs/poetry-demo-IEWSZKSE-py3.8
```
可以看出 Poetry 為我們建立了名為`poetry-demo-IEWSZKSE-py3.8`的虛擬環境。
### 重點說明
- `poetry env use python`建立虛擬環境所使用的 Python 版本,取決於`python`指令在你的 PATH 是連結到哪個版本。同理,你也可以將指令明示為`use python3``use python3.8`,只要這些指令確實存在 PATH 中。
- 預設上Poetry 會統一將虛擬環境建立在「**特定目錄**」裡,比如本例中存放的路徑是`/Users/kyo/Library/Caches/pypoetry/virtualenvs`
- 虛擬環境的**命名模式為`專案名稱-亂數-Python版本`。**
老實說我個人不是很喜歡這樣的做法,因為這意味著單一專案允許建立複數個虛擬環境(比如 Python 3.7、3.8、3.9 可以各來一個),**彈性之餘也增加了混亂的可能**,而且這命名模式我也不太欣賞,顯得過於僵化且冗長。
既然 Python 的虛擬環境理論上都是**高度綁定專案本身**的,我更偏好`venv`式的做法,也就是**把虛擬環境放到專案目錄內**,而非統一放在獨立的目錄,讓虛擬環境與專案呈現**直觀的一對一關係**。
所幸Poetry 具備這樣的選項。
---
### 修改`config`,建立專案內的`.venv`虛擬環境
我們先使用`poetry config`指令來查看 Poetry 目前幾個主要的設定,需要`--list`這個參數:
```
poetry config --list
cache-dir = "/Users/kyo/Library/Caches/pypoetry"
experimental.new-installer = true
installer.parallel = true
virtualenvs.create = true
virtualenvs.in-project = false
virtualenvs.path = "{cache-dir}/virtualenvs"
```
其中`virtualenvs.create = true`若改成`false`,則可以停止 Poetry 在「偵測不到虛擬環境時會自行建立」的行為模式,但建議還是不要更動。
`virtualenvs.in-project = false`就是我們要修改的目標,使用指令:
```
poetry config virtualenvs.in-project true
```
好,我們先把之前建立的虛擬環境刪除:
```
poetry env remove python
Deleted virtualenv: /Users/kyo/Library/Caches/pypoetry/virtualenvs/poetry-demo-IEWSZKSE-py3.8
```
重新建立,看看行為有何差異:
```
poetry env use python
Creating virtualenv poetry-demo in /Users/kyo/Documents/code/poetry-demo/.venv
Using virtualenv: /Users/kyo/Documents/code/poetry-demo/.venv
```
可以看出:
- 虛擬環境的路徑改為「**專案的根目錄**」。
- 名稱固定為`.venv`
我覺得這樣的設定更加簡潔。
### 啟動與退出虛擬環境
啟動虛擬環境,需移至專案目錄底下,使用指令`poetry shell`
1
2
3
```
poetry shell
Spawning shell within /Users/kyo/Documents/code/poetry-demo/.venv
. /Users/kyo/Documents/code/poetry-demo/.venv/bin/activate
```
`poetry shell`指令會偵測當前目錄或所屬上層目錄是否存在`pyproject.toml`來確定所要啟動的虛擬環境,所以如果不移至專案目錄,則會出現下列錯誤:
```
poetry shell
RuntimeError
Poetry could not find a pyproject.toml file in /Users/kyo/Documents/code or its parents
at ~/Library/Application Support/pypoetry/venv/lib/python3.8/site-packages/poetry/core/factory.py:369 in locate
365│ if poetry_file.exists():
366│ return poetry_file
367│
368│ else:
→ 369│ raise RuntimeError(
370│ "Poetry could not find a pyproject.toml file in {} or its parents".format(
371│ cwd
372│ )
373│ )
```
可以看到Poetry 的錯誤訊息非常清楚,讓你很容易知曉修正的方向,這是作為一個優秀命令列工具的必要條件。
退出就簡單多了,只需要`exit`即可。
---
## Poetry 常用指令
Poetry 是一個獨立的命令列工具,就像 pyenv它有自己的指令需要花費額外的心力學習且較 pip 更加複雜,這可能是使用 Poetry 的**第二道關卡**。好在常用的指令,其實也不超過 10 個,下面就來一一介紹。
在此我們繼續使用前面提過的 Flask 和 Black 套件,來示範並說明 Poetry 的優勢以及它和 pip 的不同之處。
### Poetry 新增套件
使用指令:
```
poetry add
```
相當於`pip install`,我們來試著安裝 Flask 看看會有什麼變化:
![](https://i.imgur.com/H7pPtsk.png)
圖中可以看出 Poetry 漂亮的命令列資訊呈現,會清楚告知總共新增了幾個套件。
此時專案中的`pyproject.toml`也會發生變化:
```
...
[tool.poetry.dependencies]
python = "^3.8"
Flask = "^2.1.1" # 新增部分
[tool.poetry.dev-dependencies]
[build-system]
...
```
這裡要說明,安裝 Flask則`pyproject.toml`就只會新增記載`Flask = "^2.1.1"`這個 **top-level** 的 package 項目,其餘的依賴套件**不會**直接記錄在`toml`檔中。
我覺得這是一大優點,方便區分哪些是你**主動安裝**的主要套件,而哪些又是基於套件的依賴關係而一併安裝的依賴套件。
### poetry.lock 與更新順序
除了更新`pyproject.toml`,此時專案中還會新增一個檔案,名為`poetry.lock`,它實際上就相當於 pip 的`requirements.txt`,詳細記載了所有安裝的套件與版本。
當你使用`poetry add`指令時Poetry 會**自動依序**幫你做完這三件事:
1. 更新`pyproject.toml`。
2. 依照`pyproject.toml`的內容,更新`poetry.lock`。
3. 依照`poetry.lock`的內容,更新虛擬環境。
由此可見,`poetry.lock`的內容是取決於`pyproject.toml`,但兩者並不會自己連動,一定要基於特定指令才會進行同步與更新,`poetry add`就是一個典型案例。
此時專案目錄結構如下:
```
poetry-demo
├── poetry.lock
└── pyproject.toml
0 directories, 2 files
```
### 更新 poetry.lock
當你自行修改了`pyproject.toml`內容,比如變更特定套件的版本(這是有可能的,尤其在手動處理版本衝突的時候),此時`poetry.lock`的內容與`pyproject.toml`出現了「**脫鉤**」,必須讓它依照新的`pyproject.toml`內容更新、同步,使用指令:
```
poetry lock
```
如此一來,才能確保手動修改的內容,也更新到`poetry.lock`中,畢竟虛擬環境如果要重新建立,是基於`poetry.lock`的內容來安裝套件,而非`pyproject.toml`。
還是那句話:`poetry.lock`相當於 Poetry 的`requirements.txt`。
---
### 列出全部套件清單
類似`pip list`,這裡要使用`poetry show`
```
poetry show
black 22.3.0 The uncompromising code formatter.
click 8.1.3 Composable command line interface toolkit
flask 2.1.2 A simple framework for building complex web applications.
importlib-metadata 4.11.4 Read metadata from Python packages
itsdangerous 2.1.2 Safely pass data to untrusted environments and back.
jinja2 3.1.2 A very fast and expressive template engine.
markupsafe 2.1.1 Safely add untrusted strings to HTML/XML markup.
mypy-extensions 0.4.3 Experimental type system extensions for programs checked...
pathspec 0.9.0 Utility library for gitignore style pattern matching of ...
platformdirs 2.5.2 A small Python module for determining appropriate platfo...
...
```
特別提醒的是,這裡的清單內容**並不是來自於虛擬環境**,這點和 pip 不同,而是來自於`poetry.lock`的內容。
你可能會想,來自於`poetry.lock`或虛擬環境,有差嗎?兩者不是應該要一致?
沒錯,理論上是,但也有不一致的時候,比如你使用了`pip install`指令安裝套件,就不會記載在`poetry.lock`中,那`poetry show`自然也不會顯示。
### 「樹狀」顯示套件依賴層級
Poetry 最為人津津樂道的就是它的樹狀顯示——`poetry show --tree`。
```
poetry show --tree
flask 2.1.1 A simple framework for building complex web applications.
├── click >=8.0
│ └── colorama *
├── importlib-metadata >=3.6.0
│ └── zipp >=0.5
├── itsdangerous >=2.0
├── jinja2 >=3.0
│ └── markupsafe >=2.0
└── werkzeug >=2.0
black 22.3.0 The uncompromising code formatter.
├── click >=8.0.0
│ └── colorama *
├── mypy-extensions >=0.4.3
├── pathspec >=0.9.0
├── platformdirs >=2
├── tomli >=1.1.0
└── typing-extensions >=3.10.0.0
```
讓主要套件與其依賴套件的**關係與層次,一目了然**。
而且很貼心的是,它也可以**只顯示「指定套件」**的依賴層級,以`celery`為例:
```
poetry show celery --tree
celery 4.4.0 Distributed Task Queue.
├── billiard >=3.6.1,<4.0
├── kombu >=4.6.7,<4.7
│ ├── amqp >=2.6.0,<2.7
│ │ └── vine >=1.1.3,<5.0.0a1
│ └── importlib-metadata >=0.18
│ ├── typing-extensions >=3.6.4
│ └── zipp >=0.5
├── pytz >0.0-dev
└── vine 1.3.0
```
### 安裝套件至 dev-dependencies
有些套件,比如`pytest`、`flake8`等等,**只會在開發環境中使用**,產品的**部署環境**並不需要安裝。
Poetry 允許你區分這兩者,將上述的套件安裝至`dev-dependencies`區塊,方便讓你**輕鬆建立一份不包含 dev 套件的安裝清單**。
在此以 Black 為例,安裝方式如下:
```
poetry add black -D
```
```
poetry add black --dev
```
結果的區別顯示在`pyproject.toml`裡:
```
...
[tool.poetry.dependencies]
python = "^3.8"
Flask = "^2.1.1"
[tool.poetry.dev-dependencies]
black = "^22.3.0"
...
```
可以看到`black`被列在不同區塊:`tool.poetry.dev-dependencies`。
#### 建議:明確區分 dev-dependencies
明確區分出**開發環境專用**的套件,並從部署環境中分離,我認為很有必要。
因為這些套件常常屬於「檢測型」工具,相關的**依賴套件**著實都不少!比如`flake8`,它依賴了`pycodestyle`、`pyflakes`、`mccabe`等等,這些套件都是開發環境才需要,而且它們的版本也不一定要與產品的版本一致。
還有`black`、`pre-commit`等,依賴的套件數量也都很可觀,如果不加以區分一律安裝到`dependencies`區塊,部署環境就會顯得過於**臃腫**。
常見的`dev-dependencies`區塊項目例示如下:
```toml
[tool.poetry.dev-dependencies]
flake8 = "4.0.1"
yapf = "0.32.0"
pytest = "7.1.2"
pytest-django = "4.5.2"
pytest-cov = "3.0.0"
pytest-env = "0.6.2"
pytest-sugar = "0.9.4"
pre-commit = "2.20.0"
```
---
### Poetry 移除套件
使用`poetry remove`指令。和`poetry add`一樣,可以加上`-D`參數來移除置於開發區的套件。
而移除套件時的「**依賴解析(相依性管理)**」能力,正是 Poetry 遠優於 pip 的主要環節,因為 pip 沒有嘛!也是我提議改用 Poetry 的關鍵理由——**為了順利移除套件**。
前面已經提過pip 的`pip uninstall`只會移除你所指定的套件,而不會連同依賴套件一起移除。
這是基於安裝考量,因為 pip 沒有「依賴解析」功能。如果貿然移除所有「安裝時一併安裝」的依賴套件,可能會造成巨大災難,讓別的套件失去效用。
前面也舉了 Flask 和 Black 都共同依賴`click`這個套件的例子,在手動移除套件的情況下,你可能未曾注意 Black 也依賴了`click`結果為了「徹底移除」Flask 的所有相關套件,不小心把`click`也移除掉了。
當然,我知道,絕大部分的真實情況是——你根本不會去移除一段時間前安裝但已不再使用的套件。
---
好,解釋了很多,接下來就是 Poetry 的表演了,它會幫你處理這些棘手的「套件相依性」難題,讓你輕鬆移除 Flask 而不影響 Black
![](https://i.imgur.com/79TycuL.png)
可以對比上面安裝 Flask 時的截圖,那時總共安裝了 8 個套件,但現在移除的卻只有 7 個——沒錯,因為有依賴解析,**Poetry 知道 Black 還需要**`click`!所以不能移除:
```
poetry show --tree
black 22.3.0 The uncompromising code formatter.
├── click >=8.0.0
│ └── colorama *
├── mypy-extensions >=0.4.3
├── pathspec >=0.9.0
├── platformdirs >=2
├── tomli >=1.1.0
└── typing-extensions >=3.10.0.0
```
一個套件直到環境中的**其餘套件都不再依賴它**Poetry 才會安心讓它被移除。
### 輸出 Poetry 虛擬環境的 requirements.txt
理論上,全面改用 Poetry 後,專案中是不需要存在`requirements.txt`,因為它的角色已經完全被`poetry.lock`所取代。
但事實是,你可能還需要它,甚至還需要它隨著`poetry.lock`的內容更新!至少對我而言就是如此,我在 Docker 部署環境中並不使用 Poetry所以我需要一份完全等價於`poetry.lock``requirements.txt`,用於 Docker 部署。
你可能想說,那我就在 Poetry 的虛擬環境下,使用以往熟悉的指令`pip freeze > requirements.txt`來產生一份就可以了吧?我本來也是這麼想,但實際的產出卻是如此:(提醒:目前 poetry-demo 專案中**僅剩下 Black 和它的依賴套件**
```
black @ file:///Users/kyo/Library/Caches/pypoetry/artifacts/11/4c/fc/cd6d885e9f5be135b161e365b11312cff5920d7574c8446833d7a9b1a3/black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl
click @ file:///Users/kyo/Library/Caches/pypoetry/artifacts/f0/23/09/b13d61d1fa8b3cd7c26f67505638d55002e7105849de4c4432c28e1c0d/click-8.1.2-py3-none-any.whl
mypy-extensions @ file:///Users/kyo/Library/Caches/pypoetry/artifacts/b6/a0/b0/a5dc9acd6fd12aba308634f21bb7cf0571448f20848797d7ecb327aa12/mypy_extensions-0.4.3-py2.py3-none-any.whl
...
```
這呈現好像不是我們以前熟悉的那樣:
```
black==22.3.0
click==8.1.2
mypy_extensions==0.4.3
...
```
沒錯,只要是使用`poetry add`安裝的套件,在`pip freeze`就會變成這樣。此時想輸出類似`requirements.txt`的格式,需要使用`poetry export`
預設的輸出結果會有 hash 值,很干擾閱讀。不想納入 hash 則要**加上參數**去除。**以下就是我固定用來輸出`requirements.txt`的指令與參數:**
```
poetry export -f requirements.txt -o requirements.txt --without-hashes
```
`2022/08/24`補充:網友提醒,**hash 有其價值,並建議保留**,詳見[留言區](https://github.com/kyomind/blog-reply/issues/5#issuecomment-1195904820)。
我們再看一下輸出結果,雖然不盡相同,但也相去不遠了……嗎?等等,怎麼是空白?
---
因為`poetry export`預設只會輸出`toml`中的`[tool.poetry.dependencies]`區塊的套件!還記得上面我們把 Black 安裝到`[tool.poetry.dev-dependencies]`了嗎?
顯然 Poetry 認為你的 export 需求基本上就為了部署,並不需要開發區的套件。這倒是沒錯,不過基於演示需求,我們必須輸出`[tool.poetry.dev-dependencies]`的套件,才能看到 Black。
加上`--dev`參數即可:
```
poetry export -f requirements.txt -o requirements.txt --without-hashes --dev
```
輸出的`requirements.txt`內容:
```
black==22.3.0; python_full_version >= "3.6.2"
click==8.1.2; python_version >= "3.7" and python_full_version >= "3.6.2"
colorama==0.4.4; python_version >= "3.7" and python_full_version >= "3.6.2" and platform_system == "Windows"
...
```
雖然長得有點不一樣,但這個檔案確實是可以`pip install`的。
從這裡也可以看出前面提及的「區分套件安裝區塊」的價值——有些時候並不需要輸出開發專用套件。
`poetry export`所有參數用法與說明,請參考[文件](https://python-poetry.org/docs/cli/#export)。
此時專案目錄結構如下:
```
poetry-demo
├── poetry.lock
├── pyproject.toml
└── requirements.txt
0 directories, 3 files
```
### 小結Poetry 常用指令清單
算來算去Poetry 的常用指令主要有下面幾個:
- `poetry add`
- `poetry remove`
- `poetry export`
- `poetry env use`
- `poetry shell`
- `poetry show`
- `poetry init`
- `poetry install`
其中一半,單一專案可能只會用個一兩次而已,比如`init``install``env use`,實際上需要學習的指令並不多。
那麼,只要知曉這些指令,就可以順利運用 Poetry 了嗎?可能是,也可能否,所以我下面還會再補充 Poetry 的常見使用情境與操作方式,讓你接納 Poetry 的阻力可以進一步下降!
---
## Poetry 常見使用情境與操作 QA
這部分會以「**使用場景**」的角度切入,介紹 Poetry 應用情境與操作說明,還包括一些自問自答:
1. [新增專案並使用 Poetry](https://blog.kyomind.tw/python-poetry/?fbclid=IwAR0PsmydXgpKzpbAWzJeK2ze7o0uXgGHn24xUvTCjo9fQQGGhcpnkPBG9n8#%E4%B8%80%E3%80%81%E6%96%B0%E5%A2%9E%E5%B0%88%E6%A1%88%E4%B8%A6%E4%BD%BF%E7%94%A8-Poetry)
2. [現有專案改用 Poetry](https://blog.kyomind.tw/python-poetry/?fbclid=IwAR0PsmydXgpKzpbAWzJeK2ze7o0uXgGHn24xUvTCjo9fQQGGhcpnkPBG9n8#%E4%BA%8C%E3%80%81%E7%8F%BE%E6%9C%89%E5%B0%88%E6%A1%88%E6%94%B9%E7%94%A8-Poetry)
3. [在別台主機回復專案狀態](https://blog.kyomind.tw/python-poetry/?fbclid=IwAR0PsmydXgpKzpbAWzJeK2ze7o0uXgGHn24xUvTCjo9fQQGGhcpnkPBG9n8#%E4%B8%89%E3%80%81%E5%9C%A8%E5%88%A5%E5%8F%B0%E4%B8%BB%E6%A9%9F%E5%9B%9E%E5%BE%A9%E5%B0%88%E6%A1%88%E7%8B%80%E6%85%8B)
4. [我想要重建虛擬環境](https://blog.kyomind.tw/python-poetry/?fbclid=IwAR0PsmydXgpKzpbAWzJeK2ze7o0uXgGHn24xUvTCjo9fQQGGhcpnkPBG9n8#%E5%9B%9B%E3%80%81%E6%88%91%E6%83%B3%E8%A6%81%E9%87%8D%E5%BB%BA%E8%99%9B%E6%93%AC%E7%92%B0%E5%A2%83)
5. [為什麼我不在 Docker 環境中使用 Poetry](https://blog.kyomind.tw/python-poetry/?fbclid=IwAR0PsmydXgpKzpbAWzJeK2ze7o0uXgGHn24xUvTCjo9fQQGGhcpnkPBG9n8#%E4%BA%94%E3%80%81%E7%82%BA%E4%BB%80%E9%BA%BC%E6%88%91%E4%B8%8D%E5%9C%A8-Docker-%E7%92%B0%E5%A2%83%E4%B8%AD%E4%BD%BF%E7%94%A8-Poetry%EF%BC%9F)
6. [我可以使用自己習慣的 virtualenv 嗎?](https://blog.kyomind.tw/python-poetry/?fbclid=IwAR0PsmydXgpKzpbAWzJeK2ze7o0uXgGHn24xUvTCjo9fQQGGhcpnkPBG9n8#%E5%85%AD%E3%80%81%E6%88%91%E5%8F%AF%E4%BB%A5%E4%BD%BF%E7%94%A8%E8%87%AA%E5%B7%B1%E7%BF%92%E6%85%A3%E7%9A%84-virtualenv-%E5%97%8E%EF%BC%9F)
### 一、新增專案並使用 Poetry
這是最理想的狀態,沒有過去的「包袱」,可謂是最能輕鬆採用 Poetry 的情境。
使用順序不外乎是:
1. `poetry init`:初始化,建立`pyproject.toml`
2. `poetry env use python`:建立專案虛擬環境並使用。
3. `poetry shell`:進入專案但虛擬環境還未啟動,以這個指令啟動。如果使用本指令時虛擬環境**尚未建立或已移除**,則會**直接自動幫你建立虛擬環境**並使用。
4. `poetry add`:新增套件並寫入虛擬環境。必要時使用`-D`參數新增至 dev 區塊。
5. `poetry remove`:移除套件,若是移除 dev 區塊的套件,需要加上`-D`參數。
這部分和前面內容沒有差別,因為前面內容就是以全新專案作為基礎。
### 二、現有專案改用 Poetry
極為常見的需求,但並沒有很正式的做法,因為不存在`poetry import`之類的指令。
首先要考量的就是:要怎麼把`requirements.txt`的所有項目加到`pyproject.toml`中呢?經過一番 Google基本上[只能土法煉鋼](https://stackoverflow.com/questions/62764148/how-to-import-requirements-txt-from-an-existing-project-using-poetry)
```
cat requirements.txt | xargs poetry add
```
然而這樣做是有可能遇到一些問題的,因為 Poetry **對套件的版本衝突比較敏感**,所以即便用`pip install -r requirements.txt`都能正常安裝,透過上述指令的遷移過程卻仍有機會出現錯誤。
那怎麼辦?只能照著錯誤訊息手動修正`requirements.txt`中的套件版本。
只能說這個「**手動 import**」做法實在是不得已,因為我們最早介紹`pyproject.toml`時有提到,`poetry add`只會在`pyproject.toml`中寫入「主套件」,但這樣的 import 方式相當於把`requirements.txt`中的**所有套件,都當作主套件**來`add`了!
畢竟在`requirements.txt`中**無從區分**主套件與依賴套件,都是「一視同仁」地列出。
但如此做法也讓專案的套件**失去主從之分**,這樣會有什麼**壞處**?日後要移除主套件時,**需要花額外的心力去區分主從**(因為僅僅移除依賴套件**並不會有移除效果**),比如使用`poetry show --tree`去一個一個檢視,終究是件麻煩事。
完成轉換後,為保險起見,建議透過新的`pyproject.toml`來重建一個虛擬環境。
### 三、在別台主機上重現專案的 Poetry 虛擬環境
這也是非常常見的需求。
第一步當然是`git clone`專案,此時專案中已經有 Poetry 所需的必要資訊了——也就是`pyproject.toml``poetry.lock`
你還缺少的僅僅是虛擬環境。如果是全新的主機,則還得先安裝、設定好 Poetry。
確定 Poetry 可正常使用後,移至專案目錄底下,依序執行指令:
1. `poetry env use python`:建立專案虛擬環境並使用。如果你懶得打這麼長的指令,直接`poetry shell`也是可以。此時我們會有一個「**空的**」虛擬環境。
2. `poetry install`:因為是舊專案,不需要`init`,會直接依`poetry.lock`記載的套件版本安裝到虛擬環境中!類似`npm install`
### 四、我想要重建虛擬環境
在使用專案內虛擬環境方案,也就是`.venv`的前提下,想要刪除這個虛擬環境並加以重建,也不需要使用`poetry env remove python`指令了,因為會出錯。
還有更簡單暴力的方式,是什麼呢?——直接刪除`.venv`資料夾即可。
然後再`poetry env use python``poetry shell`建一個新的就好。
### 五、為什麼我不在 Docker 環境中使用 Poetry
因為啟動容器後需要先安裝 Poetry 到全域,或打包一個帶有 Poetry 的 image兩者都會**增加新的耦合與依賴**,我覺得並不妥當。
所幸 Poetry 依舊可以輸出`requirements.txt`Docker 部署環境就繼續使用這個舊方案即可,而且 Poetry 本來主要就是用於「開發」時的套件管理,對部署差別不大。
### 六、我可以使用自己習慣的 virtualenv 嗎?
當然可以。
不過我本來也繼續使用`pyenv``virtualenv`,但兩者有時候也是會小小打架,後來還是索性用 Poetry 的虛擬環境就好。
一個專案對應一個虛擬環境,應該還是比較簡潔的做法,我的觀察啦!😎
---
## 結語:井然有序的複雜
總的來說Poetry 是一款優秀的套件管理工具,但並不像 pip 那般簡單、好上手。
使用 Poetry 來管理專案的套件與虛擬環境,需要一定的學習成本,但帶來的效益還是相當可觀的,尤其在你希望能夠乾淨且安心地移除套件之際,可謂莫它莫屬。
所以,別再猶豫,從今天起,加入 Poetry 的行列吧!
### 參考
- [https://python-poetry.org/docs/](https://python-poetry.org/docs/)
- [https://github.com/python-poetry/poetry](https://github.com/python-poetry/poetry)
- [https://github.com/python-poetry/poetry/issues/3248](https://github.com/python-poetry/poetry/issues/3248)
- [https://github.com/python-poetry/poetry/issues/5185](https://github.com/python-poetry/poetry/issues/5185)
- [Python - 取代 Pipenv 的新套件管理器 Poetry](https://note.koko.guru/posts/using-poetry-manage-python-package-environments)
- [相比 PipenvPoetry 是一個更好的選擇](https://greyli.com/poetry-a-better-choice-than-pipenv/)
- [pip, pipenv 和 poetry 的選擇](https://shazi.info/pip-pipenv-%E5%92%8C-poetry-%E7%9A%84%E9%81%B8%E6%93%87/)
- [Dependency Management With Python Poetry](https://realpython.com/dependency-management-python-poetry/)
- [Ep 15. 和 PyPA 的成員聊聊 Python 開發工作流](https://pythonhunter.org/episodes/ep15)
- [Python - Poetry](https://blog.jihongo.com/posts/2022/06/04/python-poetry/)
**相關文章**
- ### [告別 Anaconda在 macOS 上使用 pyenv 建立 Python 開發環境](https://blog.kyomind.tw/pyenv-setup/ "告別 Anaconda在 macOS 上使用 pyenv 建立 Python 開發環境")
- ### [Ubuntu 安裝使用 pyenv + pyenv-virtualenv](https://blog.kyomind.tw/ubuntu-pyenv/ "Ubuntu 安裝使用 pyenv + pyenv-virtualenv")
- ### [pyproject.toml 介紹 + VS Code 整合 Black、yapf、isort 教學](https://blog.kyomind.tw/pyproject-toml/ "pyproject.toml 介紹 + VS Code 整合 Black、yapf、isort 教學")