Python Note 01

管理套件相依性、依賴性和虛擬環境的工具。

Python Note 01

套件、虛擬環境管理工具-Poetry

使用Python來開發軟體時,管理在專案中使用的套件和Python版本是一件麻煩的事情,從一開始接觸的pip install、或是管理不同專案的虛擬環境conda來安裝不同版本的Python或是套件,都或多或少會有一些相依性的問題。

尤其是使用pip uninstall的時候,解除安裝套件時就只會解除安裝你指定的套件,其他相依的套件通常都不會被一併解除安裝,這就會遇到一些關於相依性的問題,餘下的套件可能會和之後新安裝的套件產生衝突,所以我們需要一個完整的套件相依性管理工具來處理這個問題,而Poetry就是負責管理套件相依性、依賴性和虛擬環境的工具。

poetry具體的指令或方法建議參考官方網站的指令,

安裝Poetry

安裝poetry在Windows需要使用Powershell來安裝,打開Powershell輸入以下指令(目前安裝的版本為1.6.1) :

(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | py -

如果是linux、macOS或是Windows的WSL則是用以下指令,如果需要安裝python2的poetry指令內的python3可以更改為python2:

curl -sSL https://install.python-poetry.org | python3 -

如果安裝時遇到:

Poetry installation failed.
See /home/username/poetry-installer-error-4khp4juv.log for error logs.

去看log會發現缺少distutils,這是用於協助建立和分發Python套件的工具:

ModuleNotFoundError: No module named 'distutils.cmd'

需要你安裝python3-distutils,用以下指令安裝:

sudo apt-get install python3-distutils

如果是Windows安裝完的位置會在%APPDATA%\Python\Scripts而Unix系統則是在$HOME/.local/bin上,如果要直接使用要設定環境變數PATH,Windows直接使用以下指令或是前往環境變數設定的地方新增即可,其中的使用者要記得改為自己的使用者名稱:

$Env:Path += ";C:\Users\YourUserName\AppData\Roaming\Python\Scripts"; setx PATH "$Env:Path"

Unix系統則是要在.zshrc.bashrc.bash_profile新增以下的指令:

export PATH=$PATH:$HOME/.local/bin

安裝完後會發現如果按下TAB是無法補全剩餘的字串的,如果是在Unix下使用bash,加上以下這段可以恢復補全功能:

poetry completions bash >> ~/.bash_completion

初始化Poetry專案

使用Poetry的話需要在你使用Poetry的資料夾使用以下指令,會跳出一連串的互動對話協助建立專案,其中會問你是否要以互動方式定義你的main/development依賴這兩個問題,這兩個我都選no,而最後會print出你的設定檔內容要你確認:

poetry init

結束後會新增一個pyproject.toml做為設定檔:

[tool.poetry]
name = "your_project_name"
version = "0.1.0"
description = ""
authors = ["AuthorName <[email protected]>"]
readme = "README.md"

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


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
💡
在這裡python版本號要把後面的.11也列出來,我在後續使用poetry add的時候發現套件安裝的速度非常地慢,加上後就正常了。

除了使用init外,還可以用new的方式建立專案,除了會建立pyproject.toml以外還會建構一些資料夾或檔案,後面可以接上你專案名稱將專案資料夾建立在你目前的位置上,或是加上路徑建立在你指定的資料夾內:

poetry new your_project
poetry new your_project_path

建立/進入Python虛擬環境

Poetry預設建立虛擬環境的位置在C:\Users\YourUserName\AppData\Local\pypoetry\Cache\virtualenvs,可以透過設定將建立虛擬環境的路徑改在專案下,首先使用以下指令列出poetry的設定:

poetry config --list

在Windows的設定內容如下,其中要修改virtualenvs.in-project = null,讓虛擬環境的路徑改為你專案目錄:

cache-dir = "C:\\Users\\YourUserName\\AppData\\Local\\pypoetry\\Cache"
experimental.system-git-client = false
installer.max-workers = null
installer.modern-installation = true
installer.no-binary = null
installer.parallel = true
virtualenvs.create = true
virtualenvs.in-project = null
virtualenvs.options.always-copy = false
virtualenvs.options.no-pip = false
virtualenvs.options.no-setuptools = false
virtualenvs.options.system-site-packages = false
virtualenvs.path = "{cache-dir}\\virtualenvs"  # C:\Users\YourUserName\AppData\Local\pypoetry\Cache\virtualenvs
virtualenvs.prefer-active-python = false
virtualenvs.prompt = "{project_name}-py{python_version}"

使用以下指令就可以更改設定:

poetry config virtualenvs.in-project true

用以下指令建立虛擬環境,其中python可以改為你要使用的python路徑,因為已經將虛擬環境建立的路徑改為專案內,所以在專案內會建立.venv的資料夾,也就是你的python虛擬環境:

poetry env use python

可以在Windows使用where指令或在Linux使用which找到你的python安裝路徑:

where python
which python3

建立虛擬環境後,可以使用poetry shell進入虛擬環境:

poetry shell
💡
當你的專案還沒有建立虛擬環境時,使用poetry shell也會順帶建立虛擬環境,不過是使用預設版本python來建立虛擬環境。

指令會偵測當前的目錄或上一層目錄是否存在pyporject.toml來確定要啟動的虛擬環境,如果沒有會出現以下錯誤:

> poetry shell

Poetry could not find a pyproject.toml file in /currnet/path or its parents

退出跟一般進入環境的方式一樣,使用exit即可。

Poetry常用指令

Poetry在使用的時候跟Anaconda很像,都是透過自己的指令來建立、管理你的python虛擬環境,比起Anaconda或是PyPI他使用的方式複雜了一些,需要花費一些時間來學習。

poetry add

poetry addpip installconda install一樣是用來安裝套件的,這裡示範安裝pandas,可以清楚地看到安裝了哪一些套件:

poetry add pandas
Using version ^2.1.0 for pandas

Updating dependencies
Resolving dependencies...

Writing lock file

Package operations: 5 installs, 0 updates, 0 removals

  • Installing six (1.16.0)
  • Installing python-dateutil (2.8.2)
  • Installing pytz (2023.3.post1)
  • Installing tzdata (2023.3)
  • Installing pandas (2.1.0)

打開pyproject.toml會發現在tool.poetry.dependencies多了pandas和它對應的版本號:

[tool.poetry.dependencies]
python = "3.10.10"
pandas = "^2.1.0"

poetry lock

poetry add除了會更新pyproject.toml以外,還會根據目前專案中pyproject.toml內的設定產生poetry.lock檔案,詳細記載了所有安裝的套件與對應的版本、依賴的套件。

所以大體上Poetry管理套件的流程是:

  1. 更新pyproject.toml
  2. 依照pyproject.toml內的設定更新poetry.lock
  3. 根據poetry.lock創建對應的虛擬環境.venv

所以說Poetry中的指令只要有涉及到對套件進行更改的部分,都是會按照這個流程去進行,如果不使用指令操作直接對pyproject.toml進行更改,譬如調整特定套件的版本,這時候pyproject.tomlpoetry.lock之間就失去了對應的關係,需要透過poetry lock這指令讓Poetry依照pyproject.toml內的設定,來更新poetry.lock

譬如我更改了之前安裝的pandas套件版本,^的意思是任何大於或等於 2.1.0 且小於 3.0.0的版本,所以改為 (不加上^) 2.0.0 :

[tool.poetry.dependencies]
python = "3.10.10"
pandas = "2.0.0"           # 原為^2.1.0

使用poetry lock來更新poetry.lock檔案:

> poetry lock

Updating dependencies
Resolving dependencies...

Writing lock file

poetry.lock上會看到更新後對應的版本:

# 前略
[[package]]
name = "pandas"
version = "2.0.0"
# 後略

接下來還有最後一步驟,就是把poetry.lock的更改寫入對應虛擬環境:

poetry install --sync
  • 如果當前目錄中有poetry.lock文件,poetry install會使用那裡的確切版本。
  • 如果沒有poetry.lock,會從當前專案中讀取pyproject.toml文件,解析依賴項並安裝它們。

poetry update

poetry update的作用就是更新套件,可以直接使用指令更新目前所有有用到的套件,也可以針對特定的套件進行更新:

poetry update
poetry update pandas

除此之外poetry update的實現流程和上一部分介紹的poetry lock流程是一樣的,poetry update同樣也會根據pyproject.toml來產生對應的poetry.lock,不過會把後續的poetry install也一併執行。

pandas版本改回^2.1.0,並執行poetry update:

[tool.poetry.dependencies]
python = "3.10.10"
pandas = "^2.1.0"

會得到以下結果:

Updating dependencies
Resolving dependencies...

Package operations: 0 installs, 1 update, 0 removals

  • Updating pandas (2.0.0 -> 2.1.0)

Writing lock file

更新poetry.lock的同時也會套用到虛擬環境:

>>> import pandas as pd
>>> pd.__version__
'2.1.0'

poetry show

poetry show的作用跟pip listconda list一樣,都是列出所安裝的套件,直接使用的結果如下:

numpy           1.25.2       Fundamental package for array computing in Python
pandas          2.1.0        Powerful data structures for data analysis, time series, and stat...  
python-dateutil 2.8.2        Extensions to the standard Python datetime module
pytz            2023.3.post1 World timezone definitions, modern and historical
six             1.16.0       Python 2 and 3 compatibility utilities
tzdata          2023.3       Provider of IANA time zone data

除此之外還有一個特別的功能,就是列出套件的樹狀結構,使用方法是直接在poetry show後面加上--tree參數,用這種方式可以顯示主要套件和依賴套件的關係與階級,結果如下:

pandas 2.1.0 Powerful data structures for data analysis, time series, and statistics
├── numpy >=1.22.4
├── numpy >=1.23.2
├── python-dateutil >=2.8.2
│   └── six >=1.5
├── pytz >=2020.1
└── tzdata >=2022.1

也可以列出指定的套件,如下:

poetry show pandas
poetry show pandas --tree

poetry remove

poetry remove的作用如同字面上一樣是用來移除不要的套件,在指令後面要加上要移除的套件名稱:

poetry remove pandas

從輸出結果可以看到除了把pandas移除以外,也將依賴的套件一併移除了:

Updating dependencies
Resolving dependencies...

Package operations: 0 installs, 0 updates, 6 removals

  • Removing numpy (1.25.2)
  • Removing pandas (2.0.0)
  • Removing python-dateutil (2.8.2)
  • Removing pytz (2023.3.post1)
  • Removing six (1.16.0)
  • Removing tzdata (2023.3)

Writing lock file

group

在python有一些套件可能只會在開發的時候用到,像是code formatter相關的套件blackyapf,或是負責測試的套件pytest,這些都只會在開發的時候使用到,你不會希望被包在部署環境裡面,無謂的增加大小使得部署環境過於臃腫。

所以在安裝套件的時候可以加上--group或是-G來區分開發環境或是測試環境所需安裝的套件,預設主環境的group名稱是main目前新的版本改成用group來區分,預設的主環境為tool.poetry.dependencies:

poetry add black --group dev
poetry add pytest --group test
poetry add pandas --group main

如果是第一次加上group來安裝套件,打開pyproject.toml會發現除了tool.poetry.dependencies以外多了一段,這是在group環境中所需要安裝套件,如下:

[tool.poetry.group.yourgroupname.dependencies]
package_name = "^version"

前面在介紹把poetry.lock的更改寫入對應虛擬環境會用poetry install這個指令,在還沒有建立虛擬環境的時候,要安裝不同的group的話,可以在後面添加--without--with--only;如果已經建立虛擬環境的時候,可以透過--sync選項同步環境並確保與poetry.lock匹配 :

# 排除test和docs group
poetry install --without test,docs
# 選擇test group
poetry install --with test
# 僅安裝test group
poetry install --only test

# sync 一樣可以套用with、without、only
poetry install --without test --sync
poetry install --with test --sync
poetry install --only test --sync
💡
透過--with和--only來安裝除了會安裝在所選的group中的套件外(也就是[tool.poetry.group.yourgroupname.dependencies]所列的套件外),兩者的差別為是否會安裝tool.poetry.dependencies中所列的套件。

如果想將環境還原成沒有安裝套件的狀態,可以透過以下指令,環境就只會剩下python本身的基礎套件:

poetry install --only-root --sync

具體的指令可以參考官方文件的說明。

poetry export

雖然說已經使用Poetry來管理python套件,但還是有可能會需要轉到其他沒有Poetry的環境使用,那就需要將安裝的套件匯出成requirements.txt,再到其他環境用pip install來安裝,這時候就需要poetry export:

poetry export -f requirements.txt -o requirements.txt
💡
要注意的是用這指令產生出來的requirements.txt預設只會輸出pyproject.toml中的[tool.poetry.dependencies]區塊內的套件。

如果要指定輸出的group和前面的poetry install一樣,要使用--with--without或是--only來指定要使用的group,規則和前面介紹的一樣:

poetry export -f requirements.txt -o requirements.txt --only test,docs

輸出的requirements.txt預設會有hash值,用pip install安裝會檢查這些hash:

pandas==2.1.0 ; python_full_version >= "3.10.11" and python_full_version < "4.0.0" \
    --hash=sha256:0164b85937707ec7f70b34a6c3a578dbf0f50787f910f21ca3b26a7fd3363437 \
# 下略

如果不要有hash,加上--without-hashes就行:

poetry export -f requirements.txt -o requirements.txt --without-hashes

如果要在Docker上使用Poetry來安裝專案內使用的套件,有一篇文章詳細的介紹如何用Docker透過Poetry安裝套件且產生最小化的Image。

總結常用指令

  1. poetry init/poetry new <專案路徑> : 初始化專案
  2. poetry env use <python路徑> : 建立環境用
  3. poetry shell : 進入建立的虛擬環境
  4. poetry add : 安裝套件
  5. poetry lock : 根據pyproject.toml更新poetry .lock
  6. poetry install : 根據poetry.lock更新虛擬環境
  7. poetry update : 更新套件
  8. poetry show : 列出安裝的套件
  9. poetry remove <套件名稱> : 移除指定套件
  10. poetry export:匯出安裝的套件