# 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 ```