|
| 1 | +# 划时代的 Python 包管理工具 -- PDM |
| 2 | + |
| 3 | +PDM 是一个新的 Python 的包管理器,也许你还未知晓它的存在,但实际上PDM 已经诞生两年,并在 2021 年发布 1.0 版本,目前最高的版本是 1.12.8。 |
| 4 | + |
| 5 | +在刚听到 PDM 时,我下意识认为它是 Python Development Manager,又一个和 Pipenv 和 Poetry 一样换汤不换药的虚拟环境管理工具。 |
| 6 | + |
| 7 | +一直到我翻到了作者的博客,才知道 PDM 的全称是 Python Development Master,比我想像的还要牛逼一个档次。 |
| 8 | + |
| 9 | +值得一提的是,PDM 的作者是 PyPa 成员、Pipenv 目前主要的维护者之一,最重要的是,是他是中国人,因此这是一款国人开发的工具。 |
| 10 | + |
| 11 | +## 1. Why PDM? |
| 12 | + |
| 13 | +早期的包管理器(如 Pipevn,Poetry),都是基于虚拟环境的,虚拟环境主要是为了隔离项目开发环境,但如果涉及到虚拟 环境嵌套虚拟环境,问题就难搞了,经常会出现问题。 |
| 14 | + |
| 15 | +PDM 得益于一个 2018 年的 PEP 提案(PEP582,Python local packages directory),完全摒弃了虚拟环境。 |
| 16 | + |
| 17 | +从[作者的博客](https://frostming.com/2020/02-28/pdm-introduction/)上来看,当初之所以要重复造个轮子,完全是因为 Pipenv 和 Poetry 都不够好用,正好有 PEP582 ,可以开发一个划时代的 Python 包管理工具,它就是 PDM 。 |
| 18 | + |
| 19 | +PDM 包含如下特性: |
| 20 | + |
| 21 | +- PEP 582 本地项目库目录,支持安装与运行命令,**完全不需要虚拟环境**。 |
| 22 | +- 一个简单且相对快速的依赖解析器,特别是对于大的二进制包发布。 |
| 23 | +- 兼容 PEP 517 的构建后端,用于构建发布包(源码格式与 wheel 格式) |
| 24 | +- 拥有灵活且强大的插件系统(有插件系统直接就拉开一个档次) |
| 25 | +- [PEP 621](https://www.python.org/dev/peps/pep-0621) 元数据格式 |
| 26 | +- 像 [pnpm](https://pnpm.io/motivation#saving-disk-space-and-boosting-installation-speed) 一样的中心化安装缓存,节省磁盘空间 |
| 27 | + |
| 28 | +尽管 PDM 是国人开发,但考虑到国际化,官网文档是全英文的。 |
| 29 | + |
| 30 | +我花了整整一天,通读完文档,消化了 70% 的 PDM 用法,现将心得整理分享出来,会对你上手 PDM 有帮助。 |
| 31 | + |
| 32 | +关于 PDM,内容挺多的,打算分两篇文章来完整地介绍它: |
| 33 | + |
| 34 | +- 面向新手的入门级教程 |
| 35 | +- 面向骨灰级选手的教程 |
| 36 | + |
| 37 | +本篇是第一篇,先让大家对 pdm 的基本用法有一个框架性的理解,而 pdm 真正竞争力请持续关注后续文章。 |
| 38 | + |
| 39 | +## 2. 安装 PDM |
| 40 | + |
| 41 | +PDM 的安装方法有很多种,在官网上就有 6 种,比如 pip、pipx、homebrew 等 |
| 42 | + |
| 43 | +在以前的文章中,我推荐过 pipx 工具,在安装那种命令行应用的包时非常好用。 |
| 44 | + |
| 45 | +而此时 PDM 就是一个命令行工具,因此我也推荐使用 pipx 安装,方便统一对命令行进行管理 |
| 46 | + |
| 47 | +执行 pipx install pdm 即可安装 |
| 48 | + |
| 49 | + |
| 50 | + |
| 51 | +PDM 只有 Python 3.7+ 的版本才能使用,使用其他的方法安装,要先保证你的 Python 版本,但使用 pipx 则不需要你去操心。 |
| 52 | + |
| 53 | +## 3. 初始化 PDM |
| 54 | + |
| 55 | +执行 pdm init 就会开始初始化,初始化的时候,会让你选择项目的一些信息 |
| 56 | + |
| 57 | +- 是否要上传 PyPI |
| 58 | +- 依赖的 Python 版本 |
| 59 | +- License 类型 |
| 60 | +- 作者信息 |
| 61 | +- 邮箱信息 |
| 62 | + |
| 63 | +我机器上有 Python 2.7 和 Python 3.10 两个版本,在初始化项目时会把机器上的所有 Python 版本都扫描出来了,会让选择项目的 Python 版本。 |
| 64 | + |
| 65 | + |
| 66 | + |
| 67 | + 完成之后,PDM 会将你的选择以 toml 格式写入 pyproject.toml 配置文件中。 |
| 68 | + |
| 69 | + |
| 70 | + |
| 71 | +## 4. PDM 用法 |
| 72 | + |
| 73 | +pdm 有非常多的命令,使用 `-h` 可以看到帮助菜单 |
| 74 | + |
| 75 | + |
| 76 | + |
| 77 | +### 4.1 安装包 |
| 78 | + |
| 79 | +和 Poetry 一样,安装使用的是 add 命令,但 pdm 的 add 比 poetry 好用,主要体现在分组,具体请关注后续文章 |
| 80 | + |
| 81 | + |
| 82 | + |
| 83 | +### 4.2 查看包 |
| 84 | + |
| 85 | +使用 pdm list 可以以列表形式列出当前环境已安装的包 |
| 86 | + |
| 87 | + |
| 88 | + |
| 89 | +再加个 `--graph` 就能以树状形式查看,直接依赖包和间接依赖包关系的层级一目了然 |
| 90 | + |
| 91 | + |
| 92 | + |
| 93 | +pdm list 还有两个选项: |
| 94 | + |
| 95 | +- `--freeze`:以 requirements.txt 的格式列出已安装的包 |
| 96 | +- `--json`:以 json 的格式列出已安装的包,但必须与 `--graph` 同时使用 |
| 97 | + |
| 98 | +要查看某个包的某体详情,直接用 pdm show 即可 |
| 99 | + |
| 100 | + |
| 101 | + |
| 102 | +### 4.3 删除包 |
| 103 | + |
| 104 | +删除包使用的是 remove 命令 |
| 105 | + |
| 106 | + |
| 107 | + |
| 108 | +### 4.4 项目配置 |
| 109 | + |
| 110 | +不加任何参数,可以打印出该项目的环境配置 |
| 111 | + |
| 112 | + |
| 113 | + |
| 114 | +想要修改的话,只要加 key 和 value 做为参数即可,以修改 pypi 镜像代理为例 |
| 115 | + |
| 116 | +原来上面是豆瓣源,现在我要改成阿里源,只需要执行如下命令,可比 poetry 方便多啦~ |
| 117 | + |
| 118 | + |
| 119 | + |
| 120 | +pdm config 里面有非常多的配置,想要一一搞清楚的可以去官网查阅:https://pdm.fming.dev/configuration/ |
| 121 | + |
| 122 | +### 4.5 运行命令 |
| 123 | + |
| 124 | +想要在 pdm 的环境中执行命令或者项目,可以使用 run 命令,若是执行项目时,有诸多参数,可以在 pyproject.toml 配置命令别名,具体用法,请往后看 |
| 125 | + |
| 126 | + |
| 127 | + |
| 128 | +### 4.6 查看环境 |
| 129 | + |
| 130 | +使用 `info` 命令,可以查看当前项目的环境信息 |
| 131 | + |
| 132 | + |
| 133 | + |
| 134 | +### 4.7 更新包 |
| 135 | + |
| 136 | +更新的话,简单的场景下,使用下面这两条即可 |
| 137 | + |
| 138 | +```bash |
| 139 | +# 更新所有包 |
| 140 | +pdm update |
| 141 | + |
| 142 | +# 更新某个包 |
| 143 | +pdm update <pkg> |
| 144 | +``` |
| 145 | + |
| 146 | +复杂的场景,pdm 也都为你考虑到了,它提供了很多选项,可以根据需要使用(以下如有解释错误,请帮忙指正) |
| 147 | + |
| 148 | +- `--save-compatible`:项目依赖可兼容的版本 |
| 149 | +- `--save-wildcard`:保存通配符版本(暂不明白) |
| 150 | +- `--save-exact`:保存有指定确切版本的包 |
| 151 | +- `--save-minimum`:保持最小版本的包 |
| 152 | +- `--update-reuse`:尽量只更新命令行中指定的包,其依赖包能不更新则不更新 |
| 153 | +- `--update-eager`:更新某个包顺带更新其依赖包(递归升级) |
| 154 | +- `--prerelease`:允许提前释放(暂不明白) |
| 155 | +- `--unconstrained`:忽略包版本的约束,可将包升级至最新版本 |
| 156 | +- `--top`:仅更新有在 pyproject.toml 的包 |
| 157 | +- `--dry-run`:试运行,而不去修改 lock 文件 |
| 158 | +- `--no-sync`:只更新 lock 文件,但不更新包 |
| 159 | + |
| 160 | +如果你的依赖包有设置分组,还可以指定分组进行更新 |
| 161 | + |
| 162 | +```bash |
| 163 | +pdm update -G security -G http |
| 164 | +``` |
| 165 | + |
| 166 | +也可以指定分组更新分组里的某个包 |
| 167 | + |
| 168 | +```bash |
| 169 | +pdm update -G security cryptography |
| 170 | +``` |
| 171 | + |
| 172 | +再加个 `-d` 就可以再指定 dev 依赖 |
| 173 | + |
| 174 | +``` bash |
| 175 | +# 更新所有的 dev 依赖 |
| 176 | +pdm update -d |
| 177 | + |
| 178 | +# 更新 dev 依赖下某个分组的某个包 |
| 179 | +pdm update -dG test pytest |
| 180 | +``` |
| 181 | + |
| 182 | +同样地,也可以指定 `--prod` 或者 `--production` 升级非 dev (即生产)的包。 |
| 183 | + |
| 184 | +### 4.8 切换 py |
| 185 | + |
| 186 | +当你在初始化 pdm 项目时,就已经选定了当前的 Python 版本和可用的 Python 版本范围,后面如果想更改,可以使用 use 命令,但版本要受之前设定的版本范围约束。 |
| 187 | + |
| 188 | +假设允许范围是 python 3.9+,当前使用的是 python 3.10,可以直接切换过去。 |
| 189 | + |
| 190 | +``` |
| 191 | +pdm use python3.9 |
| 192 | +``` |
| 193 | + |
| 194 | +## 5. 命令别名 |
| 195 | + |
| 196 | +在 pyproject.toml 添加 `[tool.pdm.scripts]` 可以设置快捷命令别名,若项目的执行有非常多的参数,这种设定别名的方法将很有用。 |
| 197 | + |
| 198 | + |
| 199 | + |
| 200 | + `[tool.pdm.scripts]` 有两种形式 |
| 201 | + |
| 202 | +```toml |
| 203 | +# 第一种 |
| 204 | +[tool.pdm.scripts] |
| 205 | +start = "python main.py" |
| 206 | + |
| 207 | +# 第一种 |
| 208 | +[tool.pdm.scripts] |
| 209 | +start = {cmd = "python main.py"} |
| 210 | +``` |
| 211 | + |
| 212 | +但若想在参数中加注释,就必须得使用第二种方法,例如这样 |
| 213 | + |
| 214 | +```toml |
| 215 | +[tool.pdm.scripts] |
| 216 | +start = {cmd = [ |
| 217 | + "flask", |
| 218 | + "run", |
| 219 | + # Important comment here about always using port 54321 |
| 220 | + "-p", "54321" |
| 221 | +]} |
| 222 | +``` |
| 223 | + |
| 224 | +除了 cmd 之外,还有两个参数 |
| 225 | + |
| 226 | +一个是 shell 参数,从输出来看你应该和看出和 cmd 的区别,和 `subprocess.Popen()` with `shell=True` 差不多一个意思 |
| 227 | + |
| 228 | + |
| 229 | + |
| 230 | +一个是 env_file 参数,可以指定配置环境变量的文件 |
| 231 | + |
| 232 | +```toml |
| 233 | +[tool.pdm.scripts] |
| 234 | +start.cmd = "flask run -p 54321" |
| 235 | +start.env_file = ".env" |
| 236 | +``` |
| 237 | + |
| 238 | +如果想要把这个环境变量的文件不仅限于某个命令,而是 pdm run 全局,可以这样配置 |
| 239 | + |
| 240 | +```toml |
| 241 | +[tool.pdm.scripts] |
| 242 | +_.env_file = ".env" |
| 243 | +``` |
| 244 | + |
| 245 | +加 `--list` 或者 `-l`可以查看所有设置的快捷别名 |
| 246 | + |
| 247 | + |
| 248 | + |
| 249 | +对于每一个快捷命令,都可以设置 pre 和 post 命令: |
| 250 | + |
| 251 | +- pre 命令:在每次快捷命令执行前会执行 |
| 252 | +- post 命令:在每次快捷命令执行后会执行 |
| 253 | + |
| 254 | +```toml |
| 255 | +[tool.pdm.scripts] |
| 256 | +pre_compress = "{{ Run BEFORE the `compress` script }}" |
| 257 | +compress = "tar czvf compressed.tar.gz data/" |
| 258 | +post_compress = "{{ Run AFTER the `compress` script }}" |
| 259 | +``` |
| 260 | + |
| 261 | +## 6. 自动补全 |
| 262 | + |
| 263 | +pdm 的命令虽多,但并不复杂,并不太需要使用自动补全,若你真的需要补全,也可以实现。 |
| 264 | + |
| 265 | +对于不同的 shell,自动补全的配置方式都不太一样,这个在官网上有详细的说明。 |
| 266 | + |
| 267 | +如果你和我一样使用的 zsh,可以参照我的配置方式。 |
| 268 | + |
| 269 | + |
| 270 | + |
| 271 | +截图中间有一步是 vim ~/.zshrc ,是将 pdm 插件配置到 zsh 中 |
| 272 | + |
| 273 | +``` |
| 274 | +plugins=(git z macos extract zsh-syntax-highlighting zsh-autosuggestions pdm) |
| 275 | +``` |
| 276 | + |
| 277 | +## 7. 方案兼容 |
| 278 | + |
| 279 | +### 其他方案迁移到 pdm |
| 280 | + |
| 281 | +pdm 足够好用,也足够开放,如果你当前使用的是其他的包管理器,比如 pipenv ,poetry,或者还在用最原始的 requirements.txt ,你也可以很方便的迁移到 pdm 中来: |
| 282 | + |
| 283 | +- 使用 pdm import -f {file} 无需初始化,直接转换 |
| 284 | +- 执行 pdm init 或者 pdm install 的时候,会自动识别你当前的依赖情况并转换 |
| 285 | + |
| 286 | +### pdm 迁移到其他方案 |
| 287 | + |
| 288 | +同样的,你也可以当 pdm 管理的项目,导出为其他方案 |
| 289 | + |
| 290 | +pyproject.toml 和 pdm.lock是 pdm 的两个核心文件。 |
| 291 | + |
| 292 | +pdm 做为一个后起之秀,也没有忘记向前兼容,它支持: |
| 293 | + |
| 294 | +- 将 pyproject.toml 转成 setup.py |
| 295 | + |
| 296 | + ```bash |
| 297 | + pdm export -f setuppy -o setup.py |
| 298 | + ``` |
| 299 | + |
| 300 | +- 将 pdm.lock 转成 requirements.txt |
| 301 | + |
| 302 | + ```bash |
| 303 | + pdm export -o requirements.txt |
| 304 | + ``` |
| 305 | + |
| 306 | +## 8. 总结一下 |
| 307 | + |
| 308 | +花了很大的力气,终于把 PDM 的基本用法给介绍完毕,相信一定会有人会提出质疑:这就是你所谓的 **划时代的包管理器** ? |
| 309 | + |
| 310 | +实际上,上面仅仅是入门操作,而 PDM 的一些核心知识,考虑到篇幅有限,我将这些进阶类的内容安排在后续文章,它将包括但不仅限于: |
| 311 | + |
| 312 | +- PDM 的原理剖析:PEP 582 提案 |
| 313 | +- 发布包的构建:PEP 517 提案 |
| 314 | +- Hook 脚本的定义与使用 |
| 315 | +- 插件管理系统与自定义插件 |
| 316 | +- 缓存管理系统的介绍 |
| 317 | + |
| 318 | +这些内容是 PDM 的核心,只有理解了这些,你才能真正用好 PDM,到那时你会感慨:**为什么 Guido 还不把这样的工具收编成标准的包管理工具?** |
0 commit comments