Poetry 基础使用指南:项目创建、依赖声明、虚拟环境与锁文件工作流
本篇指南基于 Poetry 官方文档 docs/basic-usage.md 展开,以安装 pendulum 这个 datetime 库为示例,系统讲解从创建项目骨架、指定 Python 版本、声明并添加依赖,到使用虚拟环境、理解 poetry.lock 锁定机制与依赖更新策略的完整基础工作流。读完本文,你将能够独立完成一个 Poetry 管理项目的初始化、依赖安装与日常维护,并能对照仓库源码理解每一步背后的实现机制。
从 poetry new 到标准项目骨架
创建新项目的起点是 poetry new 命令:
poetry new poetry-demo
这会在 poetry-demo 目录下生成如下结构:
poetry-demo
├── pyproject.toml
├── README.md
├── src
│ └── poetry_demo
│ └── __init__.py
└── tests
└── __init__.py
其中最核心的文件是 pyproject.toml,它编排整个项目及其依赖。当前版本生成的文件内容如下:
[project]
name = "poetry-demo"
version = "0.1.0"
description = ""
authors = [
{name = "Sébastien Eustace", email = "sebastien@eustace.io"}
]
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
]
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
几个关键约定值得注意:
- 包位置约定:Poetry 默认假设项目根目录下存在一个与
project.name同名的包。若你的包不在该位置,需通过tool.poetry.packages显式指定包及其位置。 MANIFEST.in的替代物:传统 setuptools 的MANIFEST.in被project.readme、tool.poetry.include与tool.poetry.exclude取代;其中tool.poetry.exclude会隐式地继承.gitignore的内容。- 完整的项目格式说明见 pyproject 章节。
源码视角:new 命令如何生成骨架
从源码看,new 命令直接复用 init 命令的核心逻辑。NewCommand 在 src/poetry/console/commands/new.py 中定义,它继承自 InitCommand,并额外提供以下选项:
| 选项 | 说明 |
|---|---|
-i, --interactive |
允许交互式地逐项填写项目配置 |
--name |
设置生成的包名 |
--src |
使用 src 布局(现已是默认行为,该选项已标记为即将移除) |
--flat |
使用扁平布局(包直接位于项目根目录) |
--readme |
指定 README 格式,默认为 md |
其中布局选择由 handle() 完成:
return self._init_pyproject(
project_path=path,
allow_interactive=self.option("interactive"),
layout_name="standard" if self.option("flat") else "src",
readme_format=self.option("readme") or "md",
allow_layout_creation_on_empty=True,
)
也就是说,src 布局是当前默认——这解释了为什么示例骨架中包位于 src/poetry_demo 之下。src 布局的定义非常直接,见 src/poetry/layouts/src.py:
class SrcLayout(Layout):
@property
def basedir(self) -> Path:
return Path("src")
另外还有一个实用细节:如果目标目录已存在且非空,new 命令会直接报错并提示改用 poetry init(见 new.py L75-L79):
Destination <path> exists and is not empty. Did you mean `poetry init`?
这一设计与下文"初始化既有项目"的用法正好衔接。
用 requires-python 指定 Python 版本
注意:与某些其他工具不同,Poetry 不会自动为你安装 Python 解释器。如果你要在包内直接以脚本/应用形式运行 Python 文件,必须自行准备(bring your own)解释器。
Poetry 要求你显式声明打算支持的 Python 版本范围,其"universal locking"(通用锁定)会保证项目在所有支持的 Python 版本上都可安装,且所有依赖都声明兼容这些版本。再次强调:设置 Python 版本只是声明支持范围,而不是触发解释器安装。
例如在 pyproject.toml 中:
[project]
requires-python = ">=3.9"
表示允许任何大于等于 3.9.0 的 Python 3 版本。当你运行 poetry install 时,系统上必须已存在一个满足该约束的 Python 解释器可用,Poetry 不会替你安装。
从源码看,当你通过 poetry init / poetry new 走交互式流程且未手动指定 Python 版本时,默认值会基于当前系统优先解释器自动生成,见 init.py L171-L177:
python = self.option("python")
if not python:
config = Config.create()
python = (
">="
+ Python.get_preferred_python(config, self.io).minor_version.to_string()
)
即生成的默认约束形如 >=3.x(x 为当前解释器的次版本号),之后再交给用户交互确认。
在既有项目中初始化:poetry init
如果目录里已经有代码,不想从零创建项目,可以直接在目录中交互式生成 pyproject.toml:
cd pre-existing-project
poetry init
init 命令的完整选项定义于 src/poetry/console/commands/init.py:
| 选项 | 说明 |
|---|---|
--name |
包名(默认取所在目录名的小写形式) |
--description |
包描述 |
--author |
作者名(非交互时会尝试读取 git 的 user.name / user.email) |
--python |
兼容的 Python 版本约束 |
--dependency |
可重复使用,如 requests:^2.10.0 或 requests=2.11.1 |
--dev-dependency |
开发依赖,格式同上 |
-l, --license |
许可证 |
交互式流程的核心逻辑在 _init_pyproject() 中,值得了解几个行为:
- 默认值推导:包名默认是
project_path.name.lower(),版本默认为0.1.0,作者会尝试从GitConfig()读取user.name与user.email(L129-L156)。 - 依赖识别:输入裸包名时,Poetry 会通过仓库池搜索(
self._get_pool().search(...))列出候选(默认来自 PyPI),并让你选择或补全完整包名;输入 git URL、URL、文件路径或目录等带来源的依赖则直接采用(L305-L439)。 - 自动版本选择:如果只给包名不给版本约束,Poetry 会调用
VersionSelector.find_best_candidate找到最佳版本,并自动生成 caret 约束^x.y.z(init.py L441-L460):
selector = VersionSelector(self._get_pool())
package = selector.find_best_candidate(
name, required_version, allow_prereleases=allow_prereleases, source=source
)
...
return package.pretty_name, f"^{version.to_string()}"
生成的最终内容会先打印出来,等你确认后才写入 pyproject.toml,写入失败或已有冲突的 build-system 时会中止(L104-L117)。
两种运行模式:package mode 与 non-package mode
Poetry 有两种操作模式:
- package mode(默认):适合需要把项目打包成 sdist/wheel 并发布到包索引的场景。此模式下
name、version等打包必需元数据是强制的;运行poetry install时项目本身会以 editable 模式 安装。 - non-package mode:只用 Poetry 管理依赖、不做打包分发:
[tool.poetry]
package-mode = false
此模式下 name、version 变为可选,项目无法构建发行版或发布到索引;运行 poetry install 时 Poetry 不会安装项目本身,只安装其依赖(等效于 poetry install --no-root)。各字段在 package mode 下的必填要求见 pyproject 章节。
从源码可以印证这条规则:install 命令 在执行完依赖安装后,通过 no-root 选项或 poetry.is_package_mode 属性判断是否还要安装当前项目:
if self.option("no-root") or not self.poetry.is_package_mode:
return 0
而 install 命令的帮助文本 也明确写明了这一用法:
By default, the above command will also install the current project. To install only the dependencies and not including the current project, run the command with the
--no-rootoption. If you want to use Poetry only for dependency management but not for packaging, you can set the "package-mode" to false in your pyproject.toml file.
另外,install.py L209-L218 在根项目安装失败时也会给出排障提示:用 --no-root、设置 package-mode = false,或检查是否需要设置 packages。
声明与添加依赖
手动声明
在 [project] 段的 dependencies 数组中按"包名 + 版本约束"的字符串形式声明:
[project]
# ...
dependencies = [
"pendulum (>=2.1,<3.0)"
]
Poetry 依据这些信息在包"仓库"中搜索正确的文件集合——即你在 tool.poetry.source 段注册的仓库,未注册任何额外仓库时默认使用 PyPI。
用 poetry add 自动添加
不想手动改 pyproject.toml 时,可以直接:
$ poetry add pendulum
它会自动找到合适的版本约束,并同时安装该包及其子依赖(即声明 + 解析 + 安装一步完成)。
Poetry 支持丰富的 依赖规范语法,包括 caret(^)、tilde(~)、通配符、不等号以及多重约束。本仓库自身的 pyproject.toml 就是一个真实示例,可以看到不等号约束、环境标记与 git 来源混用的写法:
dependencies = [
"poetry-core @ git+https://github.com/python-poetry/poetry-core.git",
"build (>=1.2.1,<2.0.0)",
"tomli (>=2.0.1,<3.0.0) ; python_version < '3.11'",
"xattr (>=1.0.0,<2.0.0) ; sys_platform == 'darwin'",
...
]
使用虚拟环境
默认情况下,Poetry 在 {cache-dir}/virtualenvs 下创建项目的虚拟环境。cache-dir 可通过 Poetry 配置修改(见 configuration 文档的 cache-dir 一节)。若希望虚拟环境位于项目目录内,可使用 virtualenvs.in-project 配置项。
默认配置从哪里来
这些默认值在源码中一目了然,见 src/poetry/config/config.py:
default_config: ClassVar[dict[str, Any]] = {
"cache-dir": str(DEFAULT_CACHE_DIR),
"data-dir": str(data_dir()),
"virtualenvs": {
"create": True,
"in-project": None,
"path": os.path.join("{cache-dir}", "virtualenvs"),
...
},
}
而 DEFAULT_CACHE_DIR 本身基于 platformdirs 的 user_cache_path("pypoetry") 计算,且数据目录可通过环境变量 POETRY_HOME 覆盖(见 src/poetry/locations.py)。虚拟环境路径的最终解析逻辑在 Config.virtualenvs_path:未显式设置 virtualenvs.path 时回退到 {cache-dir}/virtualenvs。
关于外部管理的虚拟环境,文档中有专门说明:
外部虚拟环境管理:Poetry 会检测并尊重一个已外部激活的既有虚拟环境。这是一个强大的机制,可视为 Poetry 内置简化环境管理的替代方案。要在其中获益,只需用你习惯的方式或工具激活虚拟环境,然后再运行任何需要操作环境的 Poetry 命令即可。
在 conda 等场景下无需再依赖 poetry run:既然你已经激活了目标环境,下面这些命令应当输出相同的 python 路径:
conda activate your_env_name
which python
poetry run which python
eval "$(poetry env activate)"
which python
用 poetry run 执行命令
运行脚本:
poetry run python your_script.py
运行命令行工具(如 pytest、black)同理:
poetry run pytest
poetry run 的实现见 src/poetry/console/commands/run.py:
def handle(self) -> int:
args = self.argument("args")
script = args[0]
scripts = self.poetry.local_config.get("scripts")
if scripts and script in scripts:
return self.run_script(scripts[script], args)
try:
return self.env.execute(*args)
except FileNotFoundError:
self.line_error(f"<error>Command not found: <c1>{script}</c1></error>")
return 1
有两点值得注意:
- 优先解析
[tool.poetry.scripts]:如果第一个参数是pyproject.toml中[tool.poetry.scripts]定义的脚本名,会走run_script()分支执行对应的可调用对象,否则直接在项目环境中执行该命令。 - 未安装脚本的告警:入口点脚本若尚未通过
poetry install安装为可执行文件,run_script()会警告sys.argv[0]可能不正确,并提示先运行poetry install(run.py L90-L98)。
虚拟环境的激活方式(poetry env activate 等)详见 managing-environments 文档。
版本约束与仓库查找机制
示例中请求 pendulum 时使用的版本约束是 >=2.1.0 <3.0.0,含义为"大于等于 2.1.0 且小于 3.0.0 的任何版本"。关于版本的详细语义、版本间关系及各类依赖写法,请参阅 Dependency specification。
关于 Poetry 如何下载正确的文件,文档给出了查找规则:
- Poetry 先取你请求的包名,在通过
repositories键注册的各仓库中搜索; - 若没有注册额外仓库,或已注册仓库中找不到该包名,则回退到 PyPI;
- 找到包后,再尝试为你的版本约束寻找最佳匹配。
安装依赖:poetry install 的两条路径
安装项目依赖只需要:
poetry install
运行后会发生两种情形之一。
没有 poetry.lock 时
如果从未运行过该命令、也不存在 poetry.lock 文件,Poetry 会解析 pyproject.toml 中列出的所有依赖并下载最新版本的文件。安装完成后,它会把下载的所有包及其精确版本写入 poetry.lock,将项目锁定到这些特定版本。应当把 poetry.lock 提交到项目仓库,使所有参与者锁定在相同的依赖版本上。
有 poetry.lock 时
若运行 poetry install 时 poetry.lock 与 pyproject.toml 同时存在(说明你或队友此前跑过 install 并提交了锁文件),Poetry 仍会解析 pyproject.toml 中列出的依赖,但使用 poetry.lock 中列出的精确版本 安装,保证团队所有成员的包版本一致。因此你的依赖未必都是最新可用版本——锁文件中记录的版本创建后可能有新版本发布。这是有意设计,确保项目不会因为依赖的意外变化而损坏。
此外还有一层校验机制:锁文件中记录了与 pyproject.toml 对应的哈希,Locker 会检查锁文件是否仍与当前 pyproject.toml 同步;同时它也会校验锁文件自身的版本兼容性,例如 locker.py L364-L382 中对版本不兼容锁文件直接要求用 poetry lock 重新生成。这与文档中"当 poetry.lock 与 pyproject.toml 不同步时,install 会显示 Warning"的说明相对应。
把 poetry.lock 提交到版本控制:应用开发者 vs 库开发者
作为应用开发者:提交 poetry.lock 以获得更可复现的构建。其价值在于:任何人搭建该项目时都会使用与你完全一致的依赖版本——CI 服务器、生产机器、团队其他成员跑在同一套依赖上,从而降低"只影响部分部署环境的 bug"的发生概率。即使你独自开发,半年后重装项目时也可以确信依赖仍然可用,尽管这期间依赖可能发布了大量新版本。
注意:即使你的
pyproject.toml中包含推荐的[build-system]段,你也可以用pip install -e .之类的命令把项目及其依赖安装进虚拟环境。但 pip 不会使用锁文件来决定依赖版本,因为 poetry-core 构建系统面向的是库开发者(见下节)。
作为库开发者:情况更复杂。你的用户是应用开发者,你的库会运行在你无法控制的环境中——应用会忽略你库的锁文件,可以使用任何满足你 pyproject.toml 约束的版本,通常是最新的兼容版本。如果库的 poetry.lock 落后于某些会"搞坏"用户环境的新依赖版本,你很可能最后一个得知。
简单的规避方式是直接省略 poetry.lock,但这会牺牲一定的可复现性与性能:没有锁文件时,测试失败的原因可能不只是代码变更,还可能是未被注意的库版本更新;而且每次安装前 Poetry 都需要先执行锁定,依赖较多时锁定时长可观。若不愿放弃可复现性与性能,可以考虑定期刷新 poetry.lock,保持更新以降低用户端突然出错的概率。
只安装依赖:--no-root
当前项目默认以 editable 模式 安装。若只想安装依赖、不安装项目本身:
poetry install --no-root
该选项在源码中的定义见 install.py L29-L31:"Do not install the root package (the current project)."。
用 poetry update 更新依赖
如前所述,poetry.lock 会阻止你自动获得依赖的最新版本。要升级到最新版本,使用 update 命令:
poetry update
它会拉取符合 pyproject.toml 约束的最新匹配版本,并用新版本更新锁文件(等效于删除 poetry.lock 后重新运行 install)。
最后再提醒一次同步警告:当 poetry.lock 与 pyproject.toml 不同步时,执行 install 类命令 Poetry 会显示 Warning——这通常意味着有人改了依赖声明却没有重新锁定,此时运行 poetry lock 或 poetry update 恢复同步即可。
小结:本指南覆盖了 Poetry 日常使用的核心闭环——poetry new / poetry init 创建骨架 → requires-python 声明解释器支持范围 → package / non-package 两种模式取舍 → 手动或 poetry add 声明依赖 → poetry run 在虚拟环境中执行 → poetry install 借助 poetry.lock 保证团队版本一致 → poetry update 受控升级。每一步均可在当前仓库的 docs/cli.md、docs/pyproject.md、docs/dependency-specification.md 与对应命令源码中进一步查证。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00