# poetry--Python 套件管理工具
使用 pip 或是基於 pip 的 pipenv 管理套件雖然很簡單方便, 不過 pip 有一個最根本的問題, 就是不會自動移除已經不再需要的相依套件。舉例來說, 以下在一個乾淨的 Python 環境中嘗試安裝 requests 套件:
```
# pipenv graph
#
```
因為是乾淨的環境, 所以顯示沒有安裝任何套件。接著安裝 requests 套件:
```
# pipenv install requests
Installing requests...
Resolving requests...
Added requests to Pipfile's [packages] ...
Installation Succeeded
Pipfile.lock (3cbc06) out of date, updating to (63bec0)...
Locking [packages] dependencies...
Building requirements...
Resolving dependencies...
Success!
Locking [dev-packages] dependencies...
Updated Pipfile.lock (70e8bf6bc774f5ca177467cab4e67d4264d0536857993326abc13ff43063bec0)!
Installing dependencies from Pipfile.lock (63bec0)...
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with
pipenv run.
```
接著同樣以 `graph` 子命令查看:
```
# pipenv graph
requests==2.31.0
├── certifi [required: >=2017.4.17, installed: 2024.2.2]
├── charset-normalizer [required: >=2,<4, installed: 3.3.2]
├── idna [required: >=2.5,<4, installed: 3.6]
└── urllib3 [required: >=1.21.1,<3, installed: 2.2.1]
```
你可以看到 requests 相依於 4 個套件, 如果將 requests 套件移除:
```
# pipenv uninstall requests
Removing requests from Pipfile.lock...
Removed requests from Pipfile category packages
Uninstalling requests...
Found existing installation: requests 2.31.0
Uninstalling requests-2.31.0:
Successfully uninstalled requests-2.31.0
Locking [packages] dependencies...
Locking [dev-packages] dependencies...
Updated Pipfile.lock (ebffa69a1fa192d1cef7cb42ad79231ca976565c5ce371a70160b3048d3cbc06)!
# pipenv graph
certifi==2024.2.2
charset-normalizer==3.3.2
idna==3.6
urllib3==2.2.1
```
你會看到雖然 requests 套件已經消失了, 但是因為相依關係而安裝的其它套件還在, 變成沒有其它套件需要用到的孤兒套件。這不僅僅只是浪費了儲存空間, 如果你的專案需要其它套件, 而這些套件會和沒有被移除的套件打架, 就可能會讓你的專案出現問題, 因此最好的方法還是把不再需要的孤兒套件移除。
為了依照上述原則管理專案中的套件, 你可以使用 [poetry](https://python-poetry.org/) 這套專案管理工具, 它就像是 pipenv 的進化版, 除了管理套件以外, 還擁有建置專案與上傳套件到 pypi 的功能, 本文僅專注在針對專案建立虛擬環境管理套件的功能上。
## 安裝 poetry
poetry 本身是使用 Python 撰寫的工具, 官方建議的安裝方式是透過 [pipx](https://pipx.pypa.io/stable/) 這個 Python 應用程式安裝工具, 因此第一步就是先安裝 pipx:
- Windows 上請使用 [scoop](https://scoop.sh/#/) 工具安裝, 我也強烈建議大家可以多利用這個工具管理安裝其它軟體。
- Linux 上就用各發行版本的套件管理工具。
- Mac 上請使用 [brew](https://brew.sh/)。
以下均以 Windows 平台展示:
```
# scoop install pipx
Installing 'pipx' (1.5.0) [64bit] from main bucket
pipx.pyz (311.8 KB) [==============================] 100%
Checking hash of pipx.pyz ... ok.
Running pre_install script...
Linking ~\scoop\apps\pipx\current => ~\scoop\apps\pipx\1.5.0
Creating shim for 'pipx'.
'pipx' (1.5.0) was installed successfully!
'pipx' suggests installing 'python'.
```
安裝完成後即可以 pipx 安裝 poetry。要注意的是, pipx 會幫 poetry 依據目前啟用的 Python 環境建立一個新的虛擬環境, 並在新建立的虛擬環境安裝 poetry, 之後 poetry 本身都會以這個虛擬環境運作, 因此在實際安裝 poetry 前, 要先啟用你偏好的 Python 版本, 以下使用 pyenv 啟用 3.10.11:
```
# pyenv shell 3.10.11
```
接著即可安裝 poetry:
```
# pipx install poetry
installed package poetry 1.8.2, installed using Python 3.10.11
These apps are now globally available
- poetry.exe
⚠️ Note: 'C:\Users\meebo\.local\bin' is not on your PATH
environment variable. These apps will not be globally
accessible until your PATH is updated. Run `pipx
ensurepath` to automatically add it, or manually modify
your PATH in your shell's config file (i.e. ~/.bashrc).
done! ✨ 🌟 ✨
```
由於 poetry 主要是在終端機下執行, 所以它會告訴你 poetry 的執行檔所在路徑並不在 PATH 環境變數中, 請記得執行 `pipx ensurepath` 自動加入:
```
# pipx ensurepath
Success! Added C:\Users\meebo\.local\bin to the PATH
environment variable.
Consider adding shell completions for pipx. Run 'pipx
completions' for instructions.
You will need to open a new terminal or re-login for the
PATH changes to take effect.
Otherwise pipx is ready to go! ✨ 🌟 ✨
```
如果想知道 poetry 的虛擬環境位置以及 Python 版本, 可以利用以下指令:
```
# pipx list
venvs are in C:\Users\meebo\AppData\Local\pipx\pipx\venvs
apps are exposed on your $PATH at C:\Users\meebo\.local\bin
manual pages are exposed at C:\Users\meebo\.local\share\man
package poetry 1.8.2, installed using Python 3.10.11
- poetry.exe
```
它也會告訴你 pipx 安裝的執行檔路徑, 並且列出 poetry 安裝時使用的 Python 環境。
請重新開啟終端機載入新的環境變數後, 測試看看 poetry 是否可正常執行:
```
# poetry --version
Poetry (version 1.8.2)
```
## 使用 poetry 建立新專案
poetry 和 pipenv 一樣, 都是以資料夾為專案, 如果要建立新專案, 可以使用 `poetry new` 指令:
```
# poetry new test1
Created package test1 in test1
```
它會在專案資料夾內建立一個同名的套件, 所以預設的資料夾內容如下:
```
# ls test1
Directory: C:\Users\meebo\code\python\test\test1
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 2024/4/2 上午 10:43 test1
d---- 2024/4/2 上午 10:43 tests
-a--- 2024/4/2 上午 10:43 270 pyproject.
toml
-a--- 2024/4/2 上午 10:43 0 README.md
```
`pyproject.toml` 是專案的設定檔, 預設內容如下:
```toml
[tool.poetry]
name = "test1"
version = "0.1.0"
description = ""
authors = ["meebox <meebox@gmail.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.10"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
```
`tool.prety` 是專案相關資訊; `tool.poetry.dependencies`;是本專案相依的套件, 稍後都會針對這個部分處理;`build-system` 則是和建置專案有關。
目前因為是空的專案, `tool.poetry.depedencues` 中只有:
```
python = "^3.10"
```
表示這個專案使用主版本序號為 3 的 Python, 這其實是一開始安裝 poetry 時候的 Python 版本。
如果專案資料夾名稱要和套件名稱不同, 可以加入 `--name` 選項:
```
# poetry new test2 --name myproc
Created package myproc in test2
```
它就會建立 test2 資料夾, 並在其中建立 myproc 套件。
## 幫舊專案建立 poetry 設定檔
如果是既有的專案想要改用 poetry 管理套件, 可以使用 `poetry init` 建立設定檔。以下以一個 test3 資料夾為例:
```
# cd test3
# poetry init --no-interaction
```
其中 `--no-interaction` 選項試讓 poetry 直接建立設定檔, 不要問問題。如果不加這個選項, poetry 就會針對設定檔的細項一一詢問。
## 建立虛擬環境
建立好專案後, 下一步就是要建立虛擬環境, 請先設定好要使用的 Python 版本:
```
# cd test1
# pyenv shell 3.11.7
```
接著就可以使用 `poetry env use` 建立虛擬環境:
```
# poetry env use 3.11.7
Creating virtualenv test1-mph4dns6-py3.11 in C:\Users\meebo\AppData\Local\pypoetry\Cache\virtualenvs
Using virtualenv: C:\Users\meebo\AppData\Local\pypoetry\Cache\virtualenvs\test1-mph4dns6-py3.11
```
它會告訴你虛擬環境的儲存位置, 虛擬環境的名稱是以 "專案名稱-XXXX-Pythn版本" 命名。
你甚至可以建立多個虛擬環境:
```
# pyenv shell 3.12.1
# poetry env use 3.12.1
Creating virtualenv test1-mph4dns6-py3.12 in C:\Users\meebo\AppData\Local\pypoetry\Cache\virtualenvs
Using virtualenv: C:\Users\meebo\AppData\Local\pypoetry\Cache\virtualenvs\test1-mph4dns6-py3.12
```
並且可以查看目前建立了哪些虛擬環境:
```
# poetry env list
test1-mph4dns6-py3.11
test1-mph4dns6-py3.12 (Activated)
```
要切換虛擬環境, 只要使用相同的指令即可:
```
# pyenv shell 3.11.7
# poetry env use 3.11
Using virtualenv: C:\Users\meebo\AppData\Local\pypoetry\Cache\virtualenvs\test1-mph4dns6-py3.11
```
如果要移除虛擬環境, 可使用 `poetry env remove`:
```
# poetry env remove test1-mph4dns6-py3.12
Deleted virtualenv: C:\Users\meebo\AppData\Local\pypoetry\Cache\virtualenvs\test1-mph4dns6-py3.12
```
參數要指定虛擬環境的名稱或是 Python 執行檔的路徑。如果只是想要刪除所有的虛擬環境, 可以加上 `--all` 選項:
```
# poetry env remove --all
Deleted virtualenv: C:\Users\meebo\AppData\Local\pypoetry\Cache\virtualenvs\test1-mph4dns6-py3.11
```
如果你希望虛擬環境可以建置在專案資料夾內, 可以設定 `virtualenvs.in-project` 為 `true`, 例如我們把 test2 設定為在專案內建立虛擬環境:
```
# poetry config --local virtualenvs.in-project true
```
其中 `--local` 選項是將設定寫入到專案內的 `poetry.toml` 檔中, 否則會寫入全域設定, 影響到所有的檔案。以下就是目前 `poetry.toml` 檔的內容:
```toml
[virtualenvs]
in-project = true
```
設定好即可建立虛擬環境:
```
# poetry env use 3.11.7
Creating virtualenv myproc in C:\Users\meebo\code\python\test\test2\.venv
Using virtualenv: C:\Users\meebo\code\python\test\test2\.venv
```
你會看到這次虛擬環境是設定在專案資料夾下的 `.venv` 中。
要注意的是, 這種方式只會建立單一個虛擬環境, 如果想要使用不同版本的 Python 建立虛擬環境, 就會把原本的虛擬環境替換掉, 重新建立成新的版本:
```
# pyenv shell 3.12.1
# poetry env use 3.12.1
Recreating virtualenv myproc in C:\Users\meebo\code\python\test\test2\.venv
Using virtualenv: C:\Users\meebo\code\python\test\test2\.venv
```
現在你可以依照相同的方式, 在 test3 資料夾下建立使用 Python 3.11.7 的虛擬環境:
```
cd ../test3
pyenv shell 3.11.7
poetry env use 3.11.7
```
接下來就會在這個資料夾中測試。
## 新增套件
有了虛擬環境後, 就可以安裝所需的套件了。這裡仍然以 requests 為例:
```
# poetry add requests
Using version ^2.31.0 for requests
Updating dependencies
Resolving dependencies... (0.3s)
Package operations: 5 installs, 0 updates, 0 removals
- Installing certifi (2024.2.2)
- Installing charset-normalizer (3.3.2)
- Installing idna (3.6)
- Installing urllib3 (2.2.1)
- Installing requests (2.31.0)
Writing lock file
```
整個加入套件的過程分為以下幾個步驟:
1. 依據虛擬環境的 Python 版本找到適用的套件版本, 你也可以像是使用 pip 那樣加入版本限制, 沒有加的話就是可用的最新版本, 本例為 2.31.0 版。
2. 將上述資訊寫入 `pyproject.toml` 檔中:
```toml
...
[tool.poetry.dependencies]
python = "^3.10"
requests = "^2.31.0"
...
```
3. 根據上述資訊解析相依的套件, 並且同樣找出適用的版本, 依據這些資訊安裝所有的套件。
4. 最後將剛剛解析出的所有套件版本資訊寫入 `poetry.lock` 檔。
最後的 `poetry.lock` 檔就是目前套件現況的快照, 透過它可以修復套件, 或是在複製專案資料夾後復原所有的套件, 稍後我們就會說明。
## 顯示套件資訊
如果需要顯示已經安裝的套件資訊, 可以使用 `poetry show`:
```
# poetry show
certifi 2024.2.2 Python package for providing...
charset-normalizer 3.3.2 The Real First Universal Cha...
idna 3.6 Internationalized Domain Nam...
requests 2.31.0 Python HTTP for Humans.
urllib3 2.2.1 HTTP library with thread-saf...
```
或是可以指定套件名稱顯示該套件的詳細資訊:
```
# poetry show requests
name : requests
version : 2.31.0
description : Python HTTP for Humans.
dependencies
- certifi >=2017.4.17
- charset-normalizer >=2,<4
- idna >=2.5,<4
- urllib3 >=1.21.1,<3
```
你可以看到套件本身以及相依套件的所有資訊。
## 移除套件
若要移除套件, 可以使用 `poetry remove`:
```
# poetry remove requests
Updating dependencies
Resolving dependencies... (0.1s)
Package operations: 0 installs, 0 updates, 5 removals
- Removing certifi (2024.2.2)
- Removing charset-normalizer (3.3.2)
- Removing idna (3.6)
- Removing requests (2.31.0)
- Removing urllib3 (2.2.1)
Writing lock file
```
你可以看到它不只移除了 requests, 也把相依的套件都移除了。移除套件的過程也一樣會修改 `ptproject.toml` 以及 `poetry.lock`。如果重新檢視套件內容, 就可以看到現在一個套件都沒有了:
```
# poetry show
#
```
## 手動增加套件
你也可以自己在 `pyproject.toml` 中加入套件, 例如加入不限制版本的 requests 套件:
```toml
...
[tool.poetry.dependencies]
python = "^3.10"
requests = "*"
...
```
修改完 `pyproject.toml` 檔後, 什麼事都不會發生, 我們必須告訴 poetry 依據目前內容修改 `poetry.lock` 檔:
```
# poetry lock
Updating dependencies
Resolving dependencies... (0.5s)
Writing lock file
```
`poetry lock` 會依據 pyproject.toml 檔的內容解析可用的套件版本, 以及相依套件的版本, 然後將解析完的資訊寫入 poetry.lock 檔。從執行結果的訊息中你也可以看到, 這只是修改檔案內容, 並沒有真的安裝套件。
要實際安裝套件, 還必須使用 `poetry install`:
```
# poetry install
Installing dependencies from lock file
Package operations: 5 installs, 0 updates, 0 removals
- Installing certifi (2024.2.2)
- Installing charset-normalizer (3.3.2)
- Installing idna (3.6)
- Installing urllib3 (2.2.1)
- Installing requests (2.31.0)
Installing the current project: test3 (0.1.0)
Warning: The current project could not be installed: [Errno 2] No such file or directory: 'C:\\Users\\meebo\\code\\python\\test\\test3\\README.md'
If you do not want to install the current project use --no-root.
If you want to use Poetry only for dependency management but not for packaging, you can disable package mode by setting package-mode = false in your pyproject.toml file.
In a future version of Poetry this warning will become an error!
```
這個指令會依照 lock 檔的內容, 安裝對應版本的套件。你可能已經注意到, 除了安裝套件外, 還有一行訊息表示會安裝目前專案 (current project), 並且顯示警告訊息, 表示目前的專案內並無法安裝。這是因為 poetry 預設你是要開發套件, 所以會連帶幫你把開發好的套件安裝到虛擬環境中, 但是 test3 資料夾是以空的資料夾執行 `poetry init` 建立, 並沒有 `poetry new` 會幫我們建立的套件資料夾, 所以無法安裝。
如果你只是想要利用 poetry 當成套件管理工具, 可以透過以下兩種方式避免上述問題:
- 加上 `--no-root` 選項, 這樣就不會進行安裝目前專案的步驟, 像是以下先重新執行 `poetry install`:
```
# poetry install
Installing dependencies from lock file
No dependencies to install or update
Installing the current project: test3 (0.1.0)
Warning: The current project could not be installed: [Errno 2] No such file or directory: 'C:\\Users\\meebo\\code\\python\\test\\test3\\README.md'
If you do not want to install the current project use --no-root.
If you want to use Poetry only for dependency management but not for packaging, you can disable package mode by setting package-mode = false in your pyproject.toml file.
In a future version of Poetry this warning will become an error!
```
雖然沒有需要安裝的套件, 不過它還是會嘗試安裝目前的專案。如果加上 `--no-root` 選項:
```
# poetry install --no-root
Installing dependencies from lock file
No dependencies to install or update
```
就不會安裝目前專案了。
- 在 `pyproject.tml` 檔中 `tool.poetry` 區中設定 `package-mode` 為 `false`:
```toml
[tool.poetry]
name = "test3"
version = "0.1.0"
description = ""
authors = ["meebox <meebox@gmail.com>"]
readme = "README.md"
package-mode = false
...
```
設定好之後可以重新執行 `poetry install`:
```
# poetry install
Installing dependencies from lock file
No dependencies to install or update
```
現在就不會安裝目前專案了。
要注意的是, 執行 `poetry install` 時, 如果找不到 `poetry.lock` 檔, 會自動執行 `poetry lock` 從 `pyproject.toml` 檔解析套件版本資訊後建立 `poetry.lock` 檔:
```
# rm *.lock
# poetry install
Updating dependencies
Resolving dependencies... (0.2s)
No dependencies to install or update
Writing lock file
```
## 啟用虛擬環境
要啟用 poetry 建立的虛擬環境, 方法如同 pipenv 工具:
- 使用 `poetry run` 在虛擬環境內執行特定的指令:
```
# poetry run pip list
Package Version
------------------ --------
certifi 2024.2.2
charset-normalizer 3.3.2
idna 3.6
pip 24.0
requests 2.31.0
setuptools 69.1.1
urllib3 2.2.1
```
這樣就可以在虛擬環境中執行 `pip list` 查看已安裝的套件資訊。
- 使用 `poetry shell` 開啟啟用虛擬環境的命令列環境:
```
# poetry shell
Spawning shell within C:\Users\meebo\AppData\Local\pypoetry\Cache\virtualenvs\test3-DOoPnzCk-py3.11
PowerShell 7.4.1
Loading personal and system profiles took 642ms.
(test3-py3.11) meebo on ~/code/python/test/test3
#
```
在命令行提示字元中會以圓括號顯示目前啟用的虛擬環境, 我們一樣可以執行指令查看已安裝的套件資訊:
```
# pip list
Package Version
------------------ --------
certifi 2024.2.2
charset-normalizer 3.3.2
idna 3.6
pip 24.0
requests 2.31.0
setuptools 69.1.1
urllib3 2.2.1
```
還可以繼續在啟用虛擬環境下執行其它指令。
## 同步套件
如果不小心移除了透過 poetry 安裝的套件, 例如在虛擬環境中使用 `pip` 移除套件:
```
# pip uninstall requests
Found existing installation: requests 2.31.0
Uninstalling requests-2.31.0:
Would remove:
c:\users\meebo\appdata\local\pypoetry\cache\virtualenvs\test3-doopnzck-py3.11\lib\site-packages\requests-2.31.0.dist-info\*
c:\users\meebo\appdata\local\pypoetry\cache\virtualenvs\test3-doopnzck-py3.11\lib\site-packages\requests\*
Proceed (Y/n)? y
Successfully uninstalled requests-2.31.0
```
如果你用 poetry 查看已安裝套件:
```
# poetry show
certifi 2024.2.2 Python package for providing...
charset-normalizer 3.3.2 The Real First Universal Cha...
idna 3.6 Internationalized Domain Nam...
requests 2.31.0 Python HTTP for Humans.
urllib3 2.2.1 HTTP library with thread-saf...
```
其中 requests 會以紅色標示, 表示實際上沒有安裝這個套件。這時不需要擔心, 因為有 `poetry.lock` 檔, 所以可以透過 `poetry install` 把套件裝回來:
```
# poetry install
Installing dependencies from lock file
Package operations: 1 install, 0 updates, 0 removals
- Installing requests (2.31.0)
```
你可以看到它從 lock 檔中依據相依套件版本關係把 requests 套件裝回來了。
要注意的是, `poetry install` 只會把 lock 檔裡有但是實際沒有的套件安裝回來, 但是並不會移除 lock 檔裡沒有但是實際有安裝的套件。舉例來說, 如果我們把 `pyproject.toml` 檔中的 requests 套件那一行移除:
```toml
...
[tool.poetry.dependencies]
python = "^3.10"
# requests = "*"
...
```
再使用 `poetry lock` 更新 lock 檔後執行 `poetry install`:
```
# poetry lock
Updating dependencies
Resolving dependencies... (0.1s)
Writing lock file
# poetry install
Installing dependencies from lock file
```
你會發現它並不會移除在 `pyproject.toml` 檔中被我們移除的 requests 套件。使用 `poetry show` 看到的是 lock 檔的內容, 所以看起來好像是沒有任何套件:
```
# poetry show
test/test3
#
```
但若是使用 `pip list` 就會看到所有的套件都還在:
```
# pip list
Package Version
------------------ --------
certifi 2024.2.2
charset-normalizer 3.3.2
idna 3.6
pip 24.0
requests 2.31.0
setuptools 69.1.1
urllib3 2.2.1
```
如果要讓安裝的套件與 lock 檔的內容一致, 移除不在 lock 檔內的套件, 就要加上 `--sync` 選項:
```
# poetry install --sync
Installing dependencies from lock file
Package operations: 0 installs, 0 updates, 6 removals
- Removing certifi (2024.2.2)
- Removing charset-normalizer (3.3.2)
- Removing idna (3.6)
- Removing requests (2.31.0)
- Removing setuptools (69.1.1)
- Removing urllib3 (2.2.1)
```
你可以看到它發現要移除 6 個套件才能與 lock 檔的內容一致。
## 從專案設定檔直接更新套件
如果不想個別進行 `lock` 與 `install` 的步驟, 可以使用整合的 `update` 。舉例來說, 如果把剛剛設定檔中對 requests 套件的註解移除:
```toml
...
[tool.poetry.dependencies]
python = "^3.10"
requests = "*"
...
```
再執行 `update` 就可以直接更新 lock 檔並且安裝套件:
```
# poetry update
Updating dependencies
Resolving dependencies... (0.6s)
Package operations: 5 installs, 0 updates, 0 removals
- Installing certifi (2024.2.2)
- Installing charset-normalizer (3.3.2)
- Installing idna (3.6)
- Installing urllib3 (2.2.1)
- Installing requests (2.31.0)
Writing lock file
```
一般來說, 你並不需要手動更改 `pyproject.toml` 檔, 再進行個別步驟更新 lock 檔與安裝套件, 而是利用 `add` 或是 `remove` 子命令來更新個別檔案與套件, 這樣會方便許多。
## 複製專案
如果要複製或是移動專案到其它位置, 只要在複製或是移動後, 在新的資料夾內執行 `poetry install` 即可, 例如:
```
# cp -r test3 test4
# cd test4
# pyenv shell 3.11.7
# poetry install
Creating virtualenv test3-VG6B1h5--py3.10 in C:\Users\meebo\AppData\Local\pypoetry\Cache\virtualenvs
Installing dependencies from lock file
Package operations: 5 installs, 0 updates, 0 removals
- Installing charset-normalizer (3.3.2): Downloading... 100 - Installing charset-normalizer (3.3.2)
- Installing idna (3.6)
- Installing urllib3 (2.2.1)
- Installing requests (2.31.0)
```
`poetry install` 在發現專案尚未建立虛擬環境時, 會自動建立虛擬環境。不過你可能已經發現, 即使我切換了 Python 版本, 它還是使用安裝 poetry 時的 Python 版本建立虛擬環境, 如果需要, 你還是可以另外建立偏好版本的虛擬環境。
從這裡就可以看到 `poetry.lock` 是很重要的檔案, 我們可以透過它快速建立一致版本套件的 Python 環境, 因此, 一般來說, 如果使用版控軟體, 也要一併把它遞交到倉庫中。另外, 這個檔案是由 `pyproject.toml` 解析後自動更新, 請不要手動修改。
```
poetry lock poetry install
(解析套件版本後) (依照 lock 檔安裝套件)
(讓 lock 檔與 toml 一致) (不會動到虛擬環境內 lock 檔沒有的套件)
pyproject.toml---------------->poetry.lock------------------->虛擬環境
^ poetry update ^ poetry install --sync ^
| (lock + install) | (讓虛擬環境內容與 lock 檔一致) |
| | (這會移除 lock 內沒有的套件) |
| | |
+---------------------------+-------------------------------+
poetry add/remove
```