大二时候第一次接触 python 编程,所使用的教材(全国计算机等级考试教材)上,所推荐的安装方式都是在 Python 官网下载安装包进行安装,这种安装方式一般会自带一个 idle 编辑器和 pip 包管理器。这种安装方式的特点就是容易把 python 环境直接搞崩溃,并且各种包之间的依赖一旦混乱,将无法使用重装以外的方式解决。
所以一般来说,经过了一段时间 python 的学习,我们往往都会接触到 Anaconda 这一神器。Anaconda 实际上是由 conda 包管理器加上一个安装好了许多科学计算库的名为 base 的虚拟环境,以及两个编辑器——Spyder 和 Jupyter 的巨型集合体。它的好处是,开箱即用,你无需关注具体的环境配置问题,也无需关注 base 中库的版本,编辑器也为你配置好,点开就能使用,并且 Syper 采用了一个类似 Matlab 的用户界面,对于做科学计算的人员来说非常友好。但 Anaconda 过于臃肿,base 中预置的那些科学计算库实际能用上的只有一小部分,编辑器或许我们会更想使用 vscode……
那我就安装一个 conda 包管理器不就行了,环境、编辑器自己配置……接下来的一段时间,你用上了 miniconda。miniconda 会在系统中存在一个 conda 命令,你可以使用 conda create env <env_name>
来创建环境,用 conda remove env <env_name> 来删除环境
,用 conda list
来列出包,用conda env list
来列出环境, conda activate <env_name>
来切换虚拟环境……真的很好用。尽管有时候会很烦连接不上的官方源,会讨厌越来越臃肿的虚拟环境,想要嵌套虚拟环境的时候却无法满足需求……但这些问题你还是能够接受的,毕竟确实没有更好的产品,使用 conda,至少你不会再因为 python 把系统搞崩溃了。
后浪推前浪,再厉害的产品,其市场也终会被新技术所蚕食(蚕食不等于取代),pdm(安装许多科学计算库)就是这样一款产品。PDM 是一个支持最新 PEP 标准的现代 Python 包和依赖项管理器。但它不仅仅是一个包管理器。它可以在各个方面提升您的开发工作流程。
- 简单快速的依赖解析器,主要用于大型二进制发行版。
- 根据 PEP 517 规范构建后端。
- 根据 PEP 621 规范解析项目元数据。
- 灵活而强大的插件系统。
- 多功能用户脚本。
- 使用 indygreg’s python-build-standalone 进行安装其他版本的 Python。
- 选择加入集中式安装缓存,参考 pnpm。
以上是官网的描述,简单的介绍了 pdm 支持的许多新特性。但这些特性也不是说每个都要、都能用得上的,只要选择符合自己需求的特性即可。
这篇文章不会详细介绍 pdm 的语法规则,因为在网络上已经有很多优秀的介绍文,我们选择将目光聚集在具体的工程方面。
安装
简单介绍一下安装方法
- 安装 Python(>3.9),Python,直接官网下载安装即可。注意,这里安装的 Python 只是 pdm 的一个基础运行环境而已,并不需要根据你的项目依赖进行选择。建议直接安装最新版。
当你的项目 python 版本与系统 python 版本一致时,该环境将被复用。如果不一致,pdm 会在项目根目录安装一个新的 python 虚拟环境。
- 使用一键安装脚本
Linux/macOS:
1
|
curl -sSL https://pdm-project.org/install-pdm.py | python3 -
|
Windows:
1
|
powershell -ExecutionPolicy ByPass -c "irm https://pdm-project.org/install-pdm.py | py -"
|
安装程序会将 PDM 安装到用户家目录中,位置取决于系统:
Linux/Unix: $HOME/.local/bin
macOS : $HOME/Library/Python/<version>/bin
Windows : %APPDATA%\Python\Scripts
可能会需要添加一下环境变量。
Windows:

不过我是使用 pipx 安装的,具体目录需要具体分析。
Linux 则需要在.bashrc/.zshrc 中添加 export PATH=~/.local/bin:$PATH
使用 pdm --version
确认是否安装成功。
Windows:
1
2
|
PS C:\Users\user> pdm --version
PDM, version 2.22.3
|
Linux:
1
2
|
❯ pdm --version
PDM, version 2.23.1
|
可以使用 pdm self update
命令来升级 pdm,不过有可能会失败……如果对版本有要求的话,可以根据官网教程使用 pipx(专门用于管理 python 工具的管理器)安装,然后通过 pipx 管理 pdm。
如何像 conda 一样使用 pdm
如果一个新手刚从 conda 切换到 pdm,他多少会存在一些逻辑上的惯性,但没关系,我们也可以按 conda 的逻辑使用 pdm,只是操作方式有些不同而已。
回忆一下我们是怎样使用 conda 的。
- 首先我们会创建出一个虚拟环境:
conda create -n env_name python=3.10
,一般 base 是不建议直接使用的。
- 然后激活这个虚拟环境,
conda activate env_name
,这时候命令行最左边应该会出现 (env_name),表明激活该环境成功了。
- 然后我们来安装依赖,
conda install --yes --file requirements.txt
或者使用 pip install -r requirements.txt
,从依赖目录中一键安装环境,当然也可以一个个手动安装。
- 随后我们新建、或者进入某个项目目录,假设为 project1 吧,在项目中,我们编写了一个 main.py,并通过
python main.app
运行它,成功运行的话就说明环境没有问题了。
- 我们又有了一个新的 project2 项目,它的依赖包和 project1 一样,所以我们直接复用了这个环境。
- 最后,我们还有一个 project3 项目,它的依赖与前面两个项目都不同,因此我们新建了一个虚拟环境,为它所用。
- 由于我的误操作,环境依赖错误,我们选择删掉这个虚拟环境,再新建一个。
……
那么,我们如何按照这个使用思路,来使用 pdm 呢?
- 首先,我们新建一个目录,这个目录存放着某一个虚拟环境。
mkdir python310
,这里使用 python3.10 作为示例,然后 cd 到这个目录。
1
2
3
4
5
6
7
8
9
10
11
12
|
❯ pdm init
Creating a pyproject.toml for PDM...
Please enter the Python interpreter to use
0. cpython@3.13 (/usr/bin/python3)
1. cpython@3.13 (/usr/bin/python3.13)
2. cpython@3.11 (/home/sukipai/.local/share/pdm/python/cpython@3.11.11/bin/python3)
Please select (0):
Virtualenv is created successfully at /home/sukipai/python310/.venv
Project name (python310):
Project version (0.1.0):
Do you want to build this project for distribution(such as wheel)?
If yes, it will be installed by default when running `pdm install`. [y/n] (n):
|
init 的时候,你无法选择不存在的解释器,但注意,如果想要的 python 版本与系统现有的不同,最后的 pdm install
选择 no,否则将会安装上你选择的版本。
1
2
3
4
5
|
If yes, it will be installed by default when running `pdm install`. [y/n] (n): n
License(SPDX name) (MIT):
Author name (hansyou):
Author email (fansong_yan@icloud.com):
Python requires('*' to allow any) (==3.13.*):
|
到这一步时,填写 ==3.10.*
。
创建环境完成后,查看一下项目信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
❯ cat pyproject.toml
[project]
name = "python310"
version = "0.1.0"
description = "Default template for PDM package"
authors = [
{name = "hansyou", email = "fansong_yan@icloud.com"},
]
dependencies = []
requires-python = "==3.10.*"
readme = "README.md"
license = {text = "MIT"}
[tool.pdm]
distribution = false
|
可以看到 requires-python = "==3.10.*"
,说明我们需要的 python 版本为 3.10,这份文件记录了项目的各类信息,可以直接进行修改。
最后,使用 pdm install
,安装项目依赖,这一步需要自行解决网络问题。到此,我们完成了虚拟环境的创建。你可能会觉得,conda 一行命令就完成的事情,为什么要弄得这么复杂麻烦呢?但实际上,通过 pdm 创建虚拟环境的过程中,我们自定义了许多配置项,包括作者名称、邮箱,开源协议等等,这些内容都记录在 pyproject.toml 中。再者,最然我写的篇幅长,但实际操作基本都是回车即可(除了 python 版本要注意一下),实际不会多用许多时间。
- 当多个项目的环境依赖相同时,如何处理呢?
虽然在创建工程的过程中,我们把整个 python310 文件夹看成了“一个”项目,但谁说它不能拆成两个的?我们只要在 python310 目录下再新建两个文件夹即可。
1
2
|
mkdir project1
mkdir project2
|
这两个文件夹下的项目将可以共用这一虚拟环境。
示例:
首先安装一个 pandas 库,pdm 安装库使用 add 命令:pdm add pandas
,运行代码使用 pdm run python main.py
1
2
3
4
5
6
7
8
9
10
11
|
# project1/main.py:
# import pandas
# print("this is project1")
❯ pdm run python project1/main.py
this is project1
# project1/main.py:
# import pandas
# print("this is project2")
❯ pdm run python project2/main.py
this is project2
|
两份代码均没有报错,说明共用了 pandas 库。
- 对于不同依赖的项目,如何处理?
针对不同 python 版本,或者包依赖的项目,我们应该新建一个虚拟环境。
1
2
|
drwxrwxr-x 6 sukipai sukipai 4096 Apr 17 08: 59 python310
drwxr-xr-x 4 sukipai sukipai 4096 Apr 14 03: 20 python311
|
比如,我建立了一个 3.10,一个 3.11 的环境,两个环境的创建、使用方法是一样的。
- 删除虚拟环境
相比 conda 来说非常简单,直接删除整个目录即可,但注意提前备份好项目代码。
总结:通过以上的学习,我们可以先用熟悉的 conda 开发模式来使用 pdm,更加熟悉的操作会使得我们对新工具的适应是逐步地,而不会因为陌生的操作而产生抵触心理。
将传统项目通过 pdm 跑起来
在这里,我使用了一个用于爬取知乎文章的开源项目来讲解,感谢原作者的付出。
项目地址:zhihu-downlad
使用这个项目的原因是它的整体架构比较简单,容易理解。

从 README 可以看出,作者是推荐使用 conda 进行安装的。
但我们不用管它,根据作者的推荐,我们新建一个 python3.8 的虚拟环境
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
❯ ==PS C:\Users\sukipai\Documents\python38> mkdir python38
PS C:\Users\sukipai\Documents\python38> cd python38
PS C:\Users\sukipai\Documents\python38> pdm init
Creating a pyproject.toml for PDM...
Please enter the Python interpreter to use
0. cpython@3.13 (C:\Users\sukipai\pipx\venvs\pdm\Scripts\python.EXE)
1. cpython@3.13 (C:\Users\sukipai\AppData\Local\Programs\Python\Python313\python.exe)
Please select (0):
Project name (python38):
Project version (0.1.0):
Do you want to build this project for distribution(such as wheel)?
If yes, it will be installed by default when running `pdm install`. [y/n] (n):
License(SPDX name) (MIT):
Author name ():
Author email ():
Python requires('*' to allow any) (==3.13.*): ==3.8.*
Project is initialized successfully
|
根据前面的教程初始化并安装包即可,注意 python 版本填写 3.8.*,pdm install
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
PS C:\Users\sukipai\Documents\python38> pdm install
WARNING: Lockfile does not exist
Updating the lock file...
INFO: The saved Python interpreter doesn't match the project's requirement. Trying to find another one.
WARNING: Project requires a python version of ==3.8.*, The virtualenv is being created for you as it cannot be matched
to the right version.
INFO: python.use_venv is on, creating a virtualenv for this project...
Successfully installed cpython@3.8.20
Version: 3.8.20
Executable: C:\Users\sukipai\AppData\Local\pdm\pdm\python\cpython@3.8.20\python.exe
Virtualenv is created successfully at C:\Users\sukipai\Documents\python38\.venv
Changes are written to pdm.lock.
0:00:00 🔒 Lock successful.
All packages are synced to date, nothing to do.
0:00:00 🎉 All complete! 0/0
|
将项目代码 clone 到这个目录,查看 requirements.txt,有以下包依赖:
1
2
3
4
5
6
|
PS C:\Users\sukipai\Documents\python38\zhihu-download> cat .\requirements.txt
requests == 2.31.0
bs4 == 0.0.2
markdownify == 0.12.1
tqdm == 4.66.2
flask == 3.0.3
|
理论上可以使用 pdm import -f .\zhihu-download\requirements.txt
来一键安装所有包依赖,但实际使用过程中用不了,可能兼容性还是有些问题。好在这个项目包依赖比较少,我们可以手动安装。
1
|
pdm add requests==2.31.0 bs4==0.0.2 markdownify==0.12.1 tqdm==4.66.2 flask==3.0.3
|
访问 localhost:5000,查看项目是否已经部署成功。

通过更加人类的方式理解项目
从现在开始,我们将不再将思维局限在 conda 的舒适圈里面,而是适应更多 pdm 带来的新变化。
- 包操作
前面已经提供了一个不错的示例
1
|
pdm add requests==2.31.0 bs4==0.0.2 markdownify==0.12.1 tqdm==4.66.2 flask==3.0.3
|
如果不带有版本号的话,则会安装最新的版本,这与其他包管理器是一致的。
删除包:
查看包依赖关系:
与 pip、conda 不同,pdm 可以在打印当前环境所有包的同时,打印它们的依赖关系。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
PS C: \Users\sukipai\Documents\python38> pdm list --graph
bs4 0.0.2 [ required: ==0.0.2 ]
└── beautifulsoup4 4.13.4 [ required: Any ]
├── soupsieve 2.6 [ required: >1.2 ]
└── typing-extensions 4.13.2 [ required: >=4.0.0 ]
flask 3.0.3 [ required: ==3.0.3 ]
├── blinker 1.8.2 [ required: >=1.6.2 ]
├── click 8.1.8 [ required: >=8.1.3 ]
│ └── colorama 0.4.6 [ required: Any ]
├── importlib-metadata 8.5.0 [ required: >=3.6.0 ]
│ └── zipp 3.20.2 [ required: >=3.20 ]
├── itsdangerous 2.2.0 [ required: >=2.1.2 ]
├── jinja2 3.1.6 [ required: >=3.1.2 ]
│ └── markupsafe 2.1.5 [ required: >=2.0 ]
└── werkzeug 3.0.6 [ required: >=3.0.0 ]
└── markupsafe 2.1.5 [ required: >=2.1.1 ]
markdownify 0.12.1 [ required: ==0.12.1 ]
├── beautifulsoup4 4.13.4 [ required: <5,>=4.9 ]
│ ├── soupsieve 2.6 [ required: >1.2 ]
│ └── typing-extensions 4.13.2 [ required: >=4.0.0 ]
└── six 1.17.0 [ required: <2,>=1.15 ]
requests 2.31.0 [ required: ==2.31.0 ]
├── certifi 2025.1.31 [ required: >=2017.4.17 ]
├── charset-normalizer 3.4.1 [ required: <4,>=2 ]
├── idna 3.10 [ required: <4,>=2.5 ]
└── urllib3 2.2.3 [ required: <3,>=1.21.1 ]
tqdm 4.66.2 [ required: ==4.66.2 ]
└── colorama 0.4.6 [ required: Any ]
|
依赖关系一目了然,再也不会像以前那样,看着十几行的包,脑中飞着无头苍蝇。
当然你也可以返璞归真:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
PS C: \Users\sukipai\Documents\python38> pdm list
╭────────────────────┬───────────┬──────────╮
│ name │ version │ location │
├────────────────────┼───────────┼──────────┤
│ beautifulsoup4 │ 4.13.4 │ │
│ blinker │ 1.8.2 │ │
│ bs4 │ 0.0.2 │ │
│ certifi │ 2025.1.31 │ │
│ charset-normalizer │ 3.4.1 │ │
│ click │ 8.1.8 │ │
│ colorama │ 0.4.6 │ │
│ Flask │ 3.0.3 │ │
│ idna │ 3.10 │ │
│ importlib_metadata │ 8.5.0 │ │
│ itsdangerous │ 2.2.0 │ │
│ Jinja2 │ 3.1.6 │ │
│ markdownify │ 0.12.1 │ │
│ MarkupSafe │ 2.1.5 │ │
│ requests │ 2.31.0 │ │
│ six │ 1.17.0 │ │
│ soupsieve │ 2.6 │ │
│ tqdm │ 4.66.2 │ │
│ typing_extensions │ 4.13.2 │ │
│ urllib3 │ 2.2.3 │ │
│ Werkzeug │ 3.0.6 │ │
│ zipp │ 3.20.2 │ │
╰────────────────────┴───────────┴──────────╯
```PS C:\Users\sukipai\Documents\python38>```
这种打印方式比较传统,但界面也比 pip、conda 美观一些。
你还可以这样,导出为 requirements.txt
```powershell
PS C: \Users\sukipai\Documents\python38> pdm list --freeze
beautifulsoup4 == 4.13.4
blinker == 1.8.2
bs4 == 0.0.2
certifi == 2025.1.31
charset-normalizer == 3.4.1
click == 8.1.8
colorama == 0.4.6
Flask == 3.0.3
idna == 3.10
importlib-metadata == 8.5.0
itsdangerous == 2.2.0
Jinja2 == 3.1.6
markdownify == 0.12.1
MarkupSafe == 2.1.5
requests == 2.31.0
six == 1.17.0
soupsieve == 2.6
tqdm == 4.66.2
typing-extensions == 4.13.2
urllib3 == 2.2.3
Werkzeug == 3.0.6
zipp == 3.20.2
|
还可以以 json 的格式打印:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
PS C: \Users\sukipai\Documents\python38> pdm list --graph --json
[
{
"package": "bs4",
"version": "0.0.2",
"required": "==0.0.2",
"dependencies": [
{
……
"package": "colorama",
"version": "0.4.6",
"required": "Any",
"dependencies": []
}
]
}
]
|
pdm list --graph --json
和 pdm list --json
输出不一样,大家可以自己试试,看看有什么区别。
pdm config
可以打印出项目信息
1
2
3
4
5
6
7
8
|
PS C: \Users\sukipai\Documents\python38> pdm config
……
log_dir = C:\Users\sukipai\AppData\Local\pdm\pdm\Logs
pypi.ignore_stored_index = False
pypi.json_api = False
pypi.url = https://pypi.org/simple
pypi.verify_ssl = True
……
|
可以看到我们使用了 pip 的官方源,你也可以换成国内的源以解决网络问题。想要修改的话,只要加 key 和 value 做为参数即可:pdm config pypi.url https://pypi.tuna.tsinghua.edu.cn/simple
。再使用 pdm config
打印,可以看到最下方多了一行自定义的配置。
1
2
|
Home configuration (C:\Users\sukipai\AppData\Local\pdm\pdm\config.toml):
pypi.url = https://pypi.tuna.tsinghua.edu.cn/simple
|
升级包:
不过升级这一块 pdm 做了许多的升级方式,大概有以下几种:
- –save-compatible:项目依赖可兼容的版本
- –save-wildcard:保存通配符版本(暂不明白)
- –save-exact:保存有指定确切版本的包
- –save-minimum:保持最小版本的包
- –update-reuse:尽量只更新命令行中指定的包,其依赖包能不更新则不更新
- –update-eager:更新某个包顺带更新其依赖包(递归升级)
- –prerelease:允许提前释放(暂不明白)
- –unconstrained:忽略包版本的约束,可将包升级至最新版本
- –top:仅更新有在 pyproject.toml 的包
- –dry-run:试运行,而不去修改 lock 文件
- –no-sync:只更新 lock 文件,但不更新包
可以根据具体需求进行选择,非团队开发的话,在保证兼容性的前提下直接 update 即可。
- 项目相关
运行某个 py 文件:pdm run python app.py
此外,在 pyproject.toml 添加 [tool.pdm.scripts] 可以设置快捷命令别名,若项目的执行有非常多的参数,这种设定别名的方法将很有用。推荐使用以下这种格式添加别名,这种格式支持注释,可读性更好。
1
2
3
4
5
6
7
|
[tool.pdm.scripts]
start = {cmd = [
"python",
"app.py",
# 这是注释
"-f", "hello world"
]}
|
然后我们使用 python run start
就可以取代 pdm run python main.py -f "hello world"
命令,不用每次都敲一大堆键盘了。
lock 文件
PDM 从名为 pdm.lock 的现有锁定文件安装软件包。此文件是安装依赖项的唯一事实来源,这一点比较类似于 git,或者说 npm 的 package.json,也有点像 nixos 的 flake.nix。通过这个文件,任何人都可以复现出开发者的环境。
以下是它所包含的信息:
- 所有软件包及其版本
- 包的文件名和哈希值
- (可选)用于下载包的源 URL(另请参阅:静态 URL)
- 每个包的依赖项和标记(另请参阅:从父级继承元数据)
以下是一份 pdm.lock 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
❯ cat pdm.lock
# This file is @generated by PDM.
# It is not intended for manual editing.
[metadata]
groups = ["default"]
strategy = ["inherit_metadata"]
lock_version = "4.5.0"
content_hash = "sha256:d15d302a6e1cca37fc7958f144de28ea189f6b7ce5a7cb67ef985af7208e660c"
[[metadata.targets]]
requires_python = "==3.11.*"
[[package]]
name = "joblib"
version = "1.4.2"
requires_python = ">=3.8"
summary = "Lightweight pipelining with Python functions"
groups = ["default"]
files = [
{file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"},
{file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"},
]
……
[[package]]
name = "tzdata"
version = "2025.1"
requires_python = ">=2"
summary = "Provider of IANA time zone data"
groups = ["default"]
files = [
{file = "tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639"},
{file = "tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694"},
]
|
我们并不需要手动去修改这一文件,因为我们在使用 pdm 命令的同时,该文件会同步更新,一共有三个行为可以更改这一文件:
- pdm sync 从锁定文件安装软件包,也就是项目刚被 clone 下来时,通过这一命令将环境复现为作者的环境。
- pdm update 将更新锁定文件,然后 pdm sync 更新,也就是更新依赖包。
- pdm install 将检查项目文件是否有更改,如果需要更新锁定文件,然后 pdm sync。
你也可以选择将某个包不加入 lock 文件,详情可以见官方文档。