用 pdm 取代 conda 进行 python 包管理——一份简易使用指南

本文介绍了如何使用 PDM(Python Dependency Manager)来管理 Python 项目中的包和依赖项,作为对传统工具如 conda 的替代。PDM 是一个现代的包管理器,支持最新的 PEP 标准,提供了快速的依赖解析、灵活的插件系统和强大的用户脚本功能。文章详细介绍了 PDM 的安装方法、如何创建和管理虚拟环境、如何安装和管理包依赖,以及如何通过 `pdm.lock` 文件确保项目的可复现性。通过具体的示例,展示了如何将传统项目迁移到 PDM,并通过 PDM 的命令行工具进行高效的包管理和项目配置。PDM 的使用不仅简化了包管理流程,还提高了开发效率和项目的可维护性。

大二时候第一次接触 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 包和依赖项管理器。但它不仅仅是一个包管理器。它可以在各个方面提升您的开发工作流程。

  1. 简单快速的依赖解析器,主要用于大型二进制发行版。
  2. 根据 PEP 517 规范构建后端。
  3. 根据 PEP 621 规范解析项目元数据。
  4. 灵活而强大的插件系统。
  5. 多功能用户脚本。
  6. 使用 indygreg’s python-build-standalone 进行安装其他版本的 Python。
  7. 选择加入集中式安装缓存,参考 pnpm。

以上是官网的描述,简单的介绍了 pdm 支持的许多新特性。但这些特性也不是说每个都要、都能用得上的,只要选择符合自己需求的特性即可。

这篇文章不会详细介绍 pdm 的语法规则,因为在网络上已经有很多优秀的介绍文,我们选择将目光聚集在具体的工程方面。

安装

简单介绍一下安装方法

  1. 安装 Python(>3.9),Python,直接官网下载安装即可。注意,这里安装的 Python 只是 pdm 的一个基础运行环境而已,并不需要根据你的项目依赖进行选择。建议直接安装最新版。

当你的项目 python 版本与系统 python 版本一致时,该环境将被复用。如果不一致,pdm 会在项目根目录安装一个新的 python 虚拟环境。

  1. 使用一键安装脚本

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:

Windows-pdm-环境变量.png

不过我是使用 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 的。

  1. 首先我们会创建出一个虚拟环境:conda create -n env_name python=3.10,一般 base 是不建议直接使用的。
  2. 然后激活这个虚拟环境,conda activate env_name,这时候命令行最左边应该会出现 (env_name),表明激活该环境成功了。
  3. 然后我们来安装依赖,conda install --yes --file requirements.txt 或者使用 pip install -r requirements.txt,从依赖目录中一键安装环境,当然也可以一个个手动安装。
  4. 随后我们新建、或者进入某个项目目录,假设为 project1 吧,在项目中,我们编写了一个 main.py,并通过 python main.app 运行它,成功运行的话就说明环境没有问题了。
  5. 我们又有了一个新的 project2 项目,它的依赖包和 project1 一样,所以我们直接复用了这个环境。
  6. 最后,我们还有一个 project3 项目,它的依赖与前面两个项目都不同,因此我们新建了一个虚拟环境,为它所用。
  7. 由于我的误操作,环境依赖错误,我们选择删掉这个虚拟环境,再新建一个。 ……

那么,我们如何按照这个使用思路,来使用 pdm 呢?

  1. 首先,我们新建一个目录,这个目录存放着某一个虚拟环境。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 版本要注意一下),实际不会多用许多时间。

  1. 当多个项目的环境依赖相同时,如何处理呢?

虽然在创建工程的过程中,我们把整个 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 库。

  1. 对于不同依赖的项目,如何处理?

针对不同 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 的环境,两个环境的创建、使用方法是一样的。

  1. 删除虚拟环境

相比 conda 来说非常简单,直接删除整个目录即可,但注意提前备份好项目代码。

总结:通过以上的学习,我们可以先用熟悉的 conda 开发模式来使用 pdm,更加熟悉的操作会使得我们对新工具的适应是逐步地,而不会因为陌生的操作而产生抵触心理。

将传统项目通过 pdm 跑起来

在这里,我使用了一个用于爬取知乎文章的开源项目来讲解,感谢原作者的付出。

项目地址:zhihu-downlad

使用这个项目的原因是它的整体架构比较简单,容易理解。

项目默认使用 conda 进行安装

从 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,查看项目是否已经部署成功。

知乎 download 主页面

通过更加人类的方式理解项目

从现在开始,我们将不再将思维局限在 conda 的舒适圈里面,而是适应更多 pdm 带来的新变化。

  1. 包操作

前面已经提供了一个不错的示例

1
pdm add requests==2.31.0 bs4==0.0.2 markdownify==0.12.1 tqdm==4.66.2 flask==3.0.3

如果不带有版本号的话,则会安装最新的版本,这与其他包管理器是一致的。

删除包:

1
pdm remove xxxx

查看包依赖关系:

与 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>```

这种打印方式比较传统但界面也比 pipconda 美观一些

你还可以这样导出为 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 --jsonpdm 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

升级包:

1
pdm update

不过升级这一块 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 即可。

  1. 项目相关

运行某个 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 命令的同时,该文件会同步更新,一共有三个行为可以更改这一文件:

  1. pdm sync 从锁定文件安装软件包,也就是项目刚被 clone 下来时,通过这一命令将环境复现为作者的环境。
  2. pdm update 将更新锁定文件,然后 pdm sync 更新,也就是更新依赖包。
  3. pdm install 将检查项目文件是否有更改,如果需要更新锁定文件,然后 pdm sync。

你也可以选择将某个包不加入 lock 文件,详情可以见官方文档

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计