先说几个关键点:PDM 默认走的是 PEP 582 模式,项目依赖直接装进 __pypackages__ 目录,压根不建虚拟环境。这和 pipenv 或 poetry 那套“换壳 venv”的思路完全不同——它是在 Python 导入机制层面做文章:把 __pypackages__/X.Y/lib 塞进 sys.path 最前面,优先级高于系统 site-packages。但这个机制有个硬前提:Python 解释器得支持 -I(isolated)模式,或者通过 python -m pdm run 启动,否则 __pypackages__ 不会被自动识别。很多新手直接 python main.py 失败,就是因为没走 pdm run 这一层。

为什么 python main.py 找不到安装的包?
这是最常踩的坑:PDM 安装的包在 __pypackages__/3.11/lib 下,但直接调用系统 Python 时,sys.path 根本不含该路径。说白了,你安装的依赖和运行环境之间缺了一座桥。
pdm run python main.py✅ 自动注入__pypackages__路径python main.py❌ 纯系统解释器,完全无视 PDM 项目结构- IDE(如 VS Code、PyCharm)得手动配置解释器为
pdm python list返回的路径,再把__pypackages__/X.Y/lib加进PYTHONPATH或设为 Sources Root
pdm add 和 pdm install 的行为差异
这两个命令容易混淆。注意:pdm add 只写 pyproject.toml 并更新 pdm.lock,并不实际装包;pdm install 才真正解压包到 __pypackages__。这种分离是 PEP 621 + lockfile 模式的标准做法,类比 npm install vs npm add 就很好理解了。
pdm add requests→ 修改pyproject.toml中[project.dependencies],生成/更新pdm.lockpdm install→ 读pdm.lock,将锁定版本装入__pypackages__pdm install --prod→ 跳过[project.optional-dependencies.dev],只装生产依赖- CI 环境更推荐用
pdm sync替代install,它跳过解析直接按 lockfile 安装,更快更确定
如何让非 PDM 命令(如 pytest、mypy)正常工作?
这类工具默认不走 pdm run,所以会找不到本地包或 dev 依赖。正确做法不是改 PATH,而是用 pdm run 包一层:
pdm run pytest tests/✅ 自动加载__pypackages__和 dev 依赖(如pytest)pdm run mypy src/✅ 同理- 也可以在
pyproject.toml的[tool.pdm.scripts]里定义别名:test = "pytest tests/",然后pdm run test - 注意:有些工具(如
black)如果通过pdm add --dev安装,它本身就是可执行脚本,pdm run black .就能直接调用,无需全局安装
迁移老项目时 pdm init 容易忽略的关键点
对于已有 requirements.txt 或 setup.py 的项目执行 pdm init,它不会自动导入依赖——只会生成空的 pyproject.toml。必须手动补全或用插件转换。
pdm import requirements.txt✅ 支持标准格式,但不处理注释或条件行(如pkg; sys_platform == "win32")pdm import setup.py✅ 仅提取install_requires,忽略extras_requirerequires-python字段必须显式写进pyproject.toml,否则pdm python install不知道该装哪个 Python 版本__pypackages__是 Git 未跟踪目录,首次pdm install后记得git add pyproject.toml pdm.lock,别漏了 lock 文件——它是复现环境的唯一依据
说到底,PEP 582 的威力不在“省掉 venv 命令”,而在于让 Python 解释器原生理解“当前项目专属依赖”。但这也意味着所有外部调用都得经由 pdm run 或显式配置 PYTHONPATH,否则就断联。这点和 Node.js 的 node_modules 行为一致,但 Python 开发者习惯性直呼 python,反而最容易在这里卡住。