本案例展示如何使用 Poetry 管理現代 Python 專案的完整生命週期,從專案建立到套件發布。

先備知識

問題背景

現代專案的挑戰

傳統的 Python 專案管理面臨以下問題:

 1傳統工作流程的痛點:
 2├── 相依性管理
 3│   ├── requirements.txt 無法鎖定間接相依性
 4│   ├── pip freeze 產生的版本可能過於嚴格
 5│   └── 開發與生產環境相依性混在一起
 6├── 虛擬環境
 7│   ├── 需要手動建立和管理
 8│   ├── 不同專案的環境容易混淆
 9│   └── 缺乏與專案的關聯性
10├── 建構與發布
11│   ├── setup.py 設定複雜
12│   ├── 發布流程需要多個工具
13│   └── 版本管理不一致
14└── 團隊協作
15    ├── 環境難以重現
16    ├── 「在我電腦上可以跑」問題
17    └── 新成員上手困難

為什麼選擇 Poetry?

特性pip + venvPoetry
相依性鎖定手動(pip freeze)自動(poetry.lock)
間接相依性追蹤完整追蹤
虛擬環境管理手動自動整合
建構系統需要額外工具內建
發布流程需要 twine內建
相依性群組無原生支援完整支援

解決方案

安裝 Poetry

1# 官方推薦的安裝方式(獨立安裝)
2curl -sSL https://install.python-poetry.org | python3 -
3
4# 或使用 pipx(推薦用於 CLI 工具)
5pipx install poetry
6
7# 確認安裝
8poetry --version

設定 Poetry

1# 在專案目錄中建立虛擬環境(推薦)
2poetry config virtualenvs.in-project true
3
4# 查看所有設定
5poetry config --list

工作流一:建立新專案

使用 poetry new(完整專案結構)

 1# 建立新專案
 2poetry new my-awesome-lib
 3
 4# 產生的目錄結構
 5my-awesome-lib/
 6├── pyproject.toml
 7├── README.md
 8├── my_awesome_lib/
 9│   └── __init__.py
10└── tests/
11    └── __init__.py
 1# 使用 src layout(推薦用於函式庫)
 2poetry new --src my-awesome-lib
 3
 4# 產生的目錄結構
 5my-awesome-lib/
 6├── pyproject.toml
 7├── README.md
 8├── src/
 9│   └── my_awesome_lib/
10│       └── __init__.py
11└── tests/
12    └── __init__.py

使用 poetry init(現有專案)

 1# 在現有目錄初始化
 2cd existing-project
 3poetry init
 4
 5# 互動式問答
 6# - Package name
 7# - Version
 8# - Description
 9# - Author
10# - License
11# - Python version
12# - Dependencies
互動式初始化範例
 1This command will guide you through creating your pyproject.toml config.
 2
 3Package name [existing-project]:  my-package
 4Version [0.1.0]:  1.0.0
 5Description []:  A useful Python package
 6Author [Your Name <you@example.com>, n to skip]:
 7License []:  MIT
 8Compatible Python versions [^3.10]:  ^3.10
 9
10Would you like to define your main dependencies interactively? (yes/no) [yes]
11Search for package to add (or leave blank to continue): requests
12...

工作流二:管理相依性

新增相依性

 1# 新增生產相依性
 2poetry add requests
 3poetry add "httpx>=0.24"
 4
 5# 新增開發相依性
 6poetry add pytest --group dev
 7poetry add ruff mypy --group dev
 8
 9# 新增文件相依性
10poetry add mkdocs mkdocs-material --group docs
11
12# 新增可選相依性(extras)
13poetry add pyyaml --optional
pyproject.toml 的變化
 1[tool.poetry.dependencies]
 2python = "^3.10"
 3requests = "^2.31.0"
 4httpx = ">=0.24"
 5
 6[tool.poetry.group.dev.dependencies]
 7pytest = "^8.0.0"
 8ruff = "^0.4.0"
 9mypy = "^1.10.0"
10
11[tool.poetry.group.docs.dependencies]
12mkdocs = "^1.5.0"
13mkdocs-material = "^9.5.0"
14
15[tool.poetry.extras]
16yaml = ["pyyaml"]

移除相依性

1# 移除生產相依性
2poetry remove requests
3
4# 移除開發相依性
5poetry remove pytest --group dev

更新相依性

 1# 更新所有相依性
 2poetry update
 3
 4# 更新特定套件
 5poetry update requests
 6
 7# 檢視可更新的套件
 8poetry show --outdated
 9
10# 檢視相依性樹
11poetry show --tree
相依性樹範例輸出
1requests 2.31.0 Python HTTP for Humans.
2├── certifi >=2017.4.17
3├── charset-normalizer >=2,<4
4├── idna >=2.5,<4
5└── urllib3 >=1.21.1,<3

工作流三:虛擬環境管理

自動環境管理

 1# 安裝所有相依性(自動建立虛擬環境)
 2poetry install
 3
 4# 僅安裝生產相依性
 5poetry install --only main
 6
 7# 安裝包含特定群組
 8poetry install --with dev,docs
 9
10# 排除特定群組
11poetry install --without docs
12
13# 安裝 extras
14poetry install --extras yaml
15poetry install --all-extras

環境操作

 1# 進入虛擬環境 shell
 2poetry shell
 3
 4# 在虛擬環境中執行命令(不進入 shell)
 5poetry run python script.py
 6poetry run pytest
 7poetry run python -c "import my_package; print(my_package.__version__)"
 8
 9# 顯示環境資訊
10poetry env info
11
12# 顯示環境路徑
13poetry env info --path
14
15# 列出所有環境
16poetry env list
17
18# 切換 Python 版本
19poetry env use python3.11
20poetry env use 3.12
21
22# 刪除環境
23poetry env remove python3.11
24poetry env remove --all
環境資訊範例輸出
 1Virtualenv
 2Python:         3.11.6
 3Implementation: CPython
 4Path:           /path/to/project/.venv
 5Executable:     /path/to/project/.venv/bin/python
 6Valid:          True
 7
 8Base
 9Platform:   darwin
10OS:         posix
11Python:     3.11.6
12Path:       /opt/homebrew/Cellar/python@3.11/3.11.6/Frameworks/Python.framework/Versions/3.11
13Executable: /opt/homebrew/Cellar/python@3.11/3.11.6/Frameworks/Python.framework/Versions/3.11/bin/python3.11

工作流四:建構與發布

建構套件

 1# 建構 sdist 和 wheel
 2poetry build
 3
 4# 僅建構 wheel
 5poetry build --format wheel
 6
 7# 建構結果
 8dist/
 9├── my_package-1.0.0-py3-none-any.whl
10└── my_package-1.0.0.tar.gz

發布到 PyPI

 1# 設定 PyPI token
 2poetry config pypi-token.pypi pypi-XXXXXXXXXXXX
 3
 4# 發布到 PyPI
 5poetry publish
 6
 7# 建構並發布(一步完成)
 8poetry publish --build
 9
10# 發布到 TestPyPI
11poetry config repositories.testpypi https://test.pypi.org/legacy/
12poetry config pypi-token.testpypi pypi-XXXXXXXXXXXX
13poetry publish --repository testpypi
版本管理
 1# 查看當前版本
 2poetry version
 3
 4# 升級版本
 5poetry version patch   # 1.0.0 -> 1.0.1
 6poetry version minor   # 1.0.0 -> 1.1.0
 7poetry version major   # 1.0.0 -> 2.0.0
 8
 9# 設定特定版本
10poetry version 2.0.0
11
12# 預發布版本
13poetry version prepatch  # 1.0.0 -> 1.0.1a0
14poetry version preminor  # 1.0.0 -> 1.1.0a0
15poetry version premajor  # 1.0.0 -> 2.0.0a0

完整 pyproject.toml 範例

 1[build-system]
 2requires = ["poetry-core>=2.0"]
 3build-backend = "poetry.core.masonry.api"
 4
 5[project]
 6name = "my-awesome-lib"
 7version = "1.0.0"
 8description = "A feature-rich Python library"
 9readme = "README.md"
10license = "MIT"
11requires-python = ">=3.10"
12authors = [
13    { name = "Your Name", email = "you@example.com" }
14]
15keywords = ["python", "library", "utilities"]
16classifiers = [
17    "Development Status :: 4 - Beta",
18    "Intended Audience :: Developers",
19    "License :: OSI Approved :: MIT License",
20    "Operating System :: OS Independent",
21    "Programming Language :: Python :: 3",
22    "Programming Language :: Python :: 3.10",
23    "Programming Language :: Python :: 3.11",
24    "Programming Language :: Python :: 3.12",
25    "Programming Language :: Python :: 3.13",
26    "Typing :: Typed",
27]
28
29[project.urls]
30Homepage = "https://github.com/yourname/my-awesome-lib"
31Documentation = "https://my-awesome-lib.readthedocs.io"
32Repository = "https://github.com/yourname/my-awesome-lib.git"
33Changelog = "https://github.com/yourname/my-awesome-lib/blob/main/CHANGELOG.md"
34
35[project.scripts]
36my-cli = "my_awesome_lib.cli:main"
37
38[project.optional-dependencies]
39yaml = ["pyyaml>=6.0"]
40all = ["my-awesome-lib[yaml]"]
41
42# ===== Poetry 特定設定 =====
43
44[tool.poetry]
45packages = [{ include = "my_awesome_lib", from = "src" }]
46
47[tool.poetry.dependencies]
48python = "^3.10"
49requests = "^2.31"
50click = "^8.1"
51
52[tool.poetry.group.dev.dependencies]
53pytest = "^8.0"
54pytest-cov = "^4.1"
55pytest-asyncio = "^0.23"
56mypy = "^1.10"
57ruff = "^0.4"
58
59[tool.poetry.group.docs]
60optional = true
61
62[tool.poetry.group.docs.dependencies]
63mkdocs = "^1.5"
64mkdocs-material = "^9.5"
65
66# ===== 工具設定 =====
67
68[tool.ruff]
69src = ["src"]
70line-length = 88
71target-version = "py310"
72
73[tool.ruff.lint]
74select = ["E", "W", "F", "I", "B", "C4", "UP"]
75
76[tool.mypy]
77python_version = "3.10"
78strict = true
79
80[tool.pytest.ini_options]
81testpaths = ["tests"]
82addopts = "-v --tb=short"

與其他工具的比較

Poetry vs pip

操作pipPoetry
安裝相依性pip install -r requirements.txtpoetry install
新增相依性手動編輯 requirements.txtpoetry add package
鎖定版本pip freeze > requirements.txt自動更新 poetry.lock
建立環境python -m venv .venv自動建立
執行命令source .venv/bin/activate && pythonpoetry run python
建構套件python -m buildpoetry build
發布套件twine upload dist/*poetry publish

Poetry vs setuptools

面向setuptoolsPoetry
設定格式pyproject.toml + 可能需要 setup.py僅 pyproject.toml
相依性管理需要額外工具內建完整支援
環境管理內建
學習曲線較陡(歷史包袱)較平緩
C 擴展支援完整不支援
生態系統最廣泛持續成長

Poetry vs Hatch

面向HatchPoetry
設計理念PEP 標準優先使用者體驗優先
相依性鎖定無內建核心功能
環境管理多環境(類似 tox)單一虛擬環境
腳本系統完整基本
建構後端hatchlingpoetry-core
適用場景開源函式庫應用程式、內部工具

實用技巧

技巧一:善用 Lock 檔案

 1# poetry.lock 的重要性
 2# - 記錄所有相依性的精確版本(包含間接相依性)
 3# - 確保團隊成員、CI/CD 使用相同版本
 4# - 應該提交到版本控制
 5
 6# 根據 lock 檔案安裝(不更新)
 7poetry install --no-update
 8
 9# 驗證 lock 檔案與 pyproject.toml 一致
10poetry check --lock
11
12# 匯出為 requirements.txt(部署用)
13poetry export -f requirements.txt -o requirements.txt
14poetry export -f requirements.txt --with dev -o requirements-dev.txt
15poetry export --without-hashes -o requirements.txt

技巧二:善用相依性群組

 1# 開發相依性
 2[tool.poetry.group.dev.dependencies]
 3pytest = "^8.0"
 4ruff = "^0.4"
 5
 6# 可選群組(預設不安裝)
 7[tool.poetry.group.docs]
 8optional = true
 9
10[tool.poetry.group.docs.dependencies]
11mkdocs = "^1.5"
12
13# CI 專用群組
14[tool.poetry.group.ci]
15optional = true
16
17[tool.poetry.group.ci.dependencies]
18pytest-cov = "^4.1"
19codecov = "^2.1"
1# 安裝特定群組
2poetry install --with docs
3poetry install --with ci
4
5# CI 環境中的安裝
6poetry install --only main,ci

技巧三:善用 Extras

1[project.optional-dependencies]
2# 功能性 extras
3yaml = ["pyyaml>=6.0"]
4async = ["httpx>=0.24", "aiofiles>=23.0"]
5
6# 完整安裝
7all = ["my-package[yaml,async]"]
1# 使用者安裝方式
2pip install my-package           # 基本功能
3pip install "my-package[yaml]"   # 包含 YAML 支援
4pip install "my-package[all]"    # 所有功能

技巧四:本地相依性和 Git 相依性

1[tool.poetry.dependencies]
2# 本地路徑相依性
3my-local-lib = { path = "../my-local-lib", develop = true }
4
5# Git 相依性
6my-git-lib = { git = "https://github.com/user/repo.git" }
7my-git-lib = { git = "https://github.com/user/repo.git", branch = "develop" }
8my-git-lib = { git = "https://github.com/user/repo.git", tag = "v1.0.0" }
9my-git-lib = { git = "https://github.com/user/repo.git", rev = "abc123" }

技巧五:平台特定相依性

1[tool.poetry.dependencies]
2# 僅 Windows
3pywin32 = { version = "^306", markers = "sys_platform == 'win32'" }
4
5# 僅 Linux
6uvloop = { version = "^0.19", markers = "sys_platform == 'linux'" }
7
8# Python 版本限制
9typing-extensions = { version = "^4.0", python = "<3.11" }

技巧六:Poetry 腳本

1[tool.poetry.scripts]
2my-cli = "my_package.cli:main"
3my-tool = "my_package.tools:run"
 1# src/my_package/cli.py
 2import click
 3
 4@click.command()
 5@click.option("--name", default="World", help="Name to greet")
 6def main(name: str) -> None:
 7    """Greet someone."""
 8    click.echo(f"Hello, {name}!")
 9
10if __name__ == "__main__":
11    main()
1# 安裝後即可使用
2my-cli --name Python
3# 輸出:Hello, Python!

常見問題與解決

問題一:相依性解析衝突

 1# 錯誤訊息
 2SolverProblemError: ...
 3
 4# 解決方法
 5# 1. 檢視衝突詳情
 6poetry show --tree
 7
 8# 2. 放寬版本限制
 9poetry add "package>=1.0,<3.0"
10
11# 3. 強制更新 lock 檔案
12poetry lock --no-update

問題二:虛擬環境問題

1# 重建虛擬環境
2poetry env remove --all
3poetry install
4
5# 指定 Python 版本
6poetry env use /usr/bin/python3.11

問題三:CI/CD 快取

 1# GitHub Actions 範例
 2- name: Install Poetry
 3  uses: snok/install-poetry@v1
 4  with:
 5    virtualenvs-create: true
 6    virtualenvs-in-project: true
 7
 8- name: Load cached venv
 9  uses: actions/cache@v4
10  with:
11    path: .venv
12    key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}
13
14- name: Install dependencies
15  if: steps.cache.outputs.cache-hit != 'true'
16  run: poetry install --no-interaction

設計權衡

面向優點缺點
相依性鎖定環境可重現、團隊一致lock 檔案衝突、更新需謹慎
一體化工具學習成本低、工作流統一與其他工具整合需調整
虛擬環境整合自動管理、不易混淆自訂環境位置需設定
建構與發布流程簡化不支援 C 擴展

練習

基礎練習:建立 Poetry 專案

目標:使用 Poetry 建立一個簡單的專案

  1. 使用 poetry new --src my-utils 建立專案
  2. 新增 requests 作為生產相依性
  3. 新增 pytestruff 作為開發相依性
  4. 執行 poetry install 並驗證環境

進階練習:設定相依性群組

目標:建立完整的相依性管理結構

  1. 建立 devdocsci 三個群組
  2. docsci 設為可選群組
  3. 練習使用 --with--without 選項

挑戰題:完整發布流程

目標:將專案發布到 TestPyPI

  1. 設定 TestPyPI repository
  2. 使用 poetry version 管理版本
  3. 執行 poetry build 建構套件
  4. 執行 poetry publish --repository testpypi 發布

延伸閱讀


返回:案例研究