Skip to content

asdf 管理 Python 版本 ​

asdf 是一个通用的多语言版本管理器(Multi-language Version Manager),通过插件(Plugin)机制统一管理 Python、Node.js、Ruby、Go、Terraform 等数十种工具的版本。如果你同时使用多种语言,asdf 可以用一套命令、一套配置文件搞定全部版本切换。

前置知识

阅读本节前,建议先了解:pyenv 安装与使用

基础概念 ​

asdf 的核心思想是"插件化 + 统一接口"。每种工具由一个插件描述:插件知道如何列出可用版本、如何下载安装、如何暴露可执行文件。asdf 本体只负责解析版本请求、读取配置文件、生成 shim(垫片)并将其加入 PATH 前端。

从 asdf 0.16 起,原本由 Bash 编写、依赖 asdf-python 等插件的旧架构被重写为 Go 语言实现(asdf v0.16+,即 "asdf 2.0")。新架构下,插件仍然沿用,但安装目录默认改为 ~/.asdf,命令也统一成 asdf plugin、asdf install、asdf set 等。

与 pyenv 的关键差异:

维度pyenvasdf
定位只管 Python通用多语言
实现语言BashBash(旧)/ Go(0.16+)
配置文件.python-version.tool-versions
Python 插件内置asdf-python 插件(默认基于 python-build)
虚拟环境pyenv-virtualenvasdf 配合 venv
生态Python 专用,插件丰富插件聚合在 asdf-plugins 仓库

需要理解的一点是:asdf-python 插件本身复用 pyenv 的 python-build 脚本下载并编译 CPython,因此编译依赖与 pyenv 相同。asdf 的价值在于把"多语言版本管理"这件事收敛到一套配置中。

语法与用法 ​

安装 asdf 有几种方式,官方推荐 Git 克隆或系统包管理器。

bash
# 方式一:Git 克隆(推荐,便于升级)
git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.16.7

# 方式二:Homebrew(macOS)
brew install asdf

# 方式三:asdf 自身也提供 Go 二进制安装脚本(0.16+)
curl -fsSL https://asdf-vm.com/install.sh | bash

安装后需要把 asdf 接入 Shell。

bash
# Zsh:写入 ~/.zshrc
echo '. "$HOME/.asdf/asdf.sh"' >> ~/.zshrc
echo '. "$HOME/.asdf/completions/asdf.bash"' >> ~/.zshrc
source ~/.zshrc

# Bash:写入 ~/.bashrc
echo '. "$HOME/.asdf/asdf.sh"' >> ~/.bashrc
source ~/.bashrc

# 验证
asdf --version
# v0.16.7

接下来安装 Python 插件并管理版本。

bash
# 添加上游插件注册表(asdf 0.16+ 需显式添加才能用短名称)
asdf plugin add python
# 效果等价于:asdf plugin add python https://github.com/asdf-community/asdf-python.git

# 查看插件列表与已安装插件
asdf plugin list

# 查看可安装的 Python 版本
asdf list all python
asdf list all python | grep -E '^3\.1[234]'

# 安装指定版本
asdf install python 3.12.7
asdf install python 3.13.1

# 设置版本(0.16+ 统一使用 set 命令)
asdf set python 3.12.7              # 当前目录 .tool-versions
asdf set -u python 3.12.7           # 用户级 ~/.tool-versions
asdf set --home python 3.12.7       # 全局($ASDF_DATA_DIR/version)

# 查看当前版本
asdf current
asdf current python

# 查看已安装版本
asdf list python

# 卸载
asdf uninstall python 3.12.7

命令名称随版本变化

asdf 0.15 及更早版本使用 asdf global / asdf local 设置版本;0.16 起统一为 asdf set、asdf set -u、asdf set --home。如果你的教程或脚本沿用旧命令,请先执行 asdf --version 确认,旧命令可能仍以兼容别名存在但已不推荐。

详细说明 ​

.tool-versions 文件 ​

asdf 的可版本化配置文件是 .tool-versions,它位于项目根目录,每行形如 <插件名> <版本>。一个文件可以同时声明多个语言。

text
# 项目根目录的 .tool-versions
python 3.13.1
nodejs 22.11.0
golang 1.23.4

asdf 的查找顺序(优先级从高到低):

优先级来源位置设置命令
1ASDF_PYTHON_VERSION 等环境变量当前 Shell手动 export
2当前目录 .tool-versions目录树(向上查找)asdf set python 3.13.1
3~/.tool-versions用户级asdf set -u python 3.13.1
4$ASDF_DATA_DIR/version全局兜底asdf set --home python 3.13.1
bash
# 在项目目录设置后查看文件内容
cd ~/work/new-app
asdf set python 3.13.1
cat .tool-versions
# python 3.13.1

# 追加另一个语言(不影响已有行)
asdf set nodejs 22.11.0
cat .tool-versions
# python 3.13.1
# nodejs 22.11.0

# legacy_version_file 兼容:让 asdf 读取 .python-version
asdf settings list | grep legacy
asdf settings set legacy_version_file true

若开启 legacy_version_file,asdf 也能识别 .python-version、.nvmrc 等既有文件,便于从 pyenv 平滑迁移。

bash
# 开启兼容模式
asdf settings set legacy_version_file true
asdf settings list

# 此时若目录下已有 .python-version 且无 .tool-versions,
# asdf 会按 .python-version 的内容解析 Python 版本

插件机制 ​

asdf 的插件本质上是一个 Git 仓库,遵循约定:必须实现 bin/list-all(列出可安装版本),可选实现 bin/install、bin/list-bin-paths、bin/exec-env 等脚本。

bash
# 查看插件源码位置
asdf plugin list --urls

# 更新插件(拉取插件仓库最新代码)
asdf plugin update python
asdf plugin update --all

# 删除插件
asdf plugin remove python

# 手动指定插件仓库地址(当短名称不存在时)
asdf plugin add python https://github.com/asdf-community/asdf-python.git

几个与 Python 相关的常用插件:

插件说明
python官方社区插件,基于 python-build 编译 CPython
python(备用)可配置为使用 --enable-shared 等选项
pdm管理 PDM 工具自身版本
poetry管理 Poetry 工具自身版本
uv用 asdf 安装 uv
bash
# 用 asdf 管理 uv 与 poetry 的版本,避免污染项目虚拟环境
asdf plugin add uv
asdf install uv 0.5.11
asdf set uv 0.5.11

asdf plugin add poetry
asdf install poetry 1.8.5
asdf set poetry 1.8.5

编译优化与镜像 ​

由于 asdf-python 复用 python-build,编译依赖与 pyenv 一致;同时也可以在安装前通过环境变量传递编译选项。

bash
# 让 CPython 启用共享库(某些场景如嵌入式或需要 libpython 时有用)
export PYTHON_CONFIGURE_OPTS="--enable-shared"
asdf install python 3.13.1

# 传递额外的 configure 参数(例如优化标志)
export PYTHON_CFLAGS="-O2 -march=native"
asdf install python 3.13.1

# 指定镜像加速源码下载(根据插件实现,通常支持以下变量)
export PYTHON_BUILD_MIRROR_URL="https://npmmirror.com/mirrors/python"
asdf install python 3.12.7

Linux 上同样需要预装构建依赖。

bash
sudo apt update
sudo apt install -y build-essential libssl-dev zlib1g-dev libbz2-dev \
  libreadline-dev libsqlite3-dev libffi-dev liblzma-dev \
  libncursesw5-dev xz-utils tk-dev

shim 机制与常见故障排查 ​

asdf 在 ~/.asdf/shims 下生成每个可执行文件的垫片,并把该目录加入 PATH。当某个命令"找不到"或"版本不对"时,通常是 shim 未重新生成或 PATH 顺序有误。

bash
# 重新生成 shim(安装新工具后)
asdf reshim python

# 查看 shim 目录
ls ~/.asdf/shims | head

# 确认某个命令来自 asdf 而非系统
which python
# /Users/demo/.asdf/shims/python

# 查看 asdf 的完整解释(为什么是当前版本)
asdf current --verbose || asdf current

# 检查 asdf 环境是否正常
asdf info
asdf doctor

不要用 sudo 安装

与 pyenv 一样,asdf 把版本安装到 ~/.asdf/installs/ 下。用 sudo 执行会让文件属主变为 root,导致后续卸载失败且需手动 chown 修复。

从 pyenv 迁移到 asdf ​

bash
# 1. 记录 pyenv 已安装的版本
pyenv versions

# 2. 安装 asdf 并添加 python 插件
asdf plugin add python

# 3. 安装对应版本
asdf install python 3.12.7

# 4. 让 asdf 兼容读取已有的 .python-version
asdf settings set legacy_version_file true

# 5. 把 .python-version 迁移为 .tool-versions(可选,推荐显式化)
asdf set python 3.12.7

# 6. 清理 pyenv 的 PATH 配置(删除 Shell 配置中的 pyenv init 行)
# 7. 卸载 pyenv 及其版本,释放磁盘

实战示例 ​

示例一:多语言单体仓库统一版本 ​

一个仓库同时包含 Python 后端、Node.js 前端与 Go 微服务,用一份 .tool-versions 声明全部版本。

text
# .tool-versions
python 3.13.1
nodejs 22.11.0
golang 1.23.4
terraform 1.10.3
bash
# 一次性安装文件声明的所有工具版本
asdf install

# 查看每个工具当前生效版本
asdf current
# golang    1.23.4   /Users/demo/work/monorepo/.tool-versions
# nodejs    22.11.0  /Users/demo/work/monorepo/.tool-versions
# python    3.13.1   /Users/demo/work/monorepo/.tool-versions
# terraform 1.10.3   /Users/demo/work/monorepo/.tool-versions

示例二:Python 脚本读取 asdf 配置并校验 ​

在 CI 或本地预检中,读取 .tool-versions 并与当前运行的解释器比对,可提前发现版本漂移。

python
"""校验当前解释器版本是否与 .tool-versions 声明一致。"""

import sys
from pathlib import Path


def parse_tool_versions(path: Path) -> dict[str, str]:
    """解析 .tool-versions 文件,返回 {插件名: 版本} 映射。"""
    result: dict[str, str] = {}
    if not path.exists():
        return result
    for raw_line in path.read_text(encoding="utf-8").splitlines():
        line = raw_line.strip()
        if not line or line.startswith("#"):
            continue  # 跳过空行与注释
        parts = line.split()
        if len(parts) >= 2:
            result[parts[0]] = parts[1]
    return result


def main() -> int:
    declared = parse_tool_versions(Path(".tool-versions"))
    expected = declared.get("python")
    current = ".".join(str(part) for part in sys.version_info[:3])
    if expected is None:
        print("未在 .tool-versions 中声明 python 版本,跳过校验")
        return 0
    if not current.startswith(".".join(expected.split(".")[:2])):
        print(f"[错误] 期望 Python {expected},当前为 {current}")
        return 1
    print(f"[通过] Python {current} 与 .tool-versions 一致")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
bash
$ python check_version.py
[通过] Python 3.13.1 与 .tool-versions 一致

# 当版本不匹配时
$ asdf set python 3.12.7 && python check_version.py
[错误] 期望 Python 3.12.7,当前为 3.13.1

示例三:在 CI 中使用 asdf ​

yaml
# .github/workflows/ci.yml(节选)
name: CI
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: 安装 asdf
        uses: asdf-vm/actions/setup@v3

      - name: 按 .tool-versions 安装工具
        run: |
          asdf plugin add python
          asdf install

      - name: 运行测试
        run: |
          python -V
          pip install -r requirements.txt
          pytest

注意事项 ​

  • Python 插件名称可能冲突:社区同时存在多个 Python 插件实现,添加前先确认插件来源,避免不同人得到不同的编译行为。
  • legacy_version_file 的歧义:同时存在 .python-version 与 .tool-versions 时,asdf 会优先使用 .tool-versions;迁移期间建议二者内容保持一致,避免误解。
  • 编译时间较长:asdf-python 从源码编译 CPython,首次安装通常需要数分钟;若想跳过编译,可考虑改用 uv python install(见 uv 工具链管理)。
  • shim 与 IDE 不兼容:IDE 未必会执行 Shell 初始化,可能读不到 asdf 的 shim,需在 IDE 中显式指定解释器绝对路径 ~/.asdf/installs/python/3.13.1/bin/python。
  • asdf reshim 忘记执行:通过 pip install 安装带入口点的工具后,需要 asdf reshim python 才能通过 shim 调用。
  • 不要混用 pyenv 与 asdf:二者都通过修改 PATH 生效,同时启用会导致版本解析混乱。选其一即可。
  • 版本文件的路径依赖:asdf set 写入的是当前目录的 .tool-versions,若在错误的目录执行会导致整个目录树版本被覆盖。

最佳实践 ​

  • 在仓库根目录提交 .tool-versions,作为团队统一的工具版本声明。
  • 多语言项目优先使用 asdf,单语言项目可权衡 pyenv 或 uv。
  • 用 asdf plugin update --all 定期更新插件,确保能装到最新 Python 版本。
  • 把 asdf 初始化脚本放在 Shell 配置的靠前位置(在修改 PATH 的其他语句之前)。
  • CI 中使用官方 asdf-vm/actions/setup 动作,避免手工拼装环境。
  • 从 pyenv 迁移时先开启 legacy_version_file,验证无误后再删除 .python-version。
  • 常用工具(uv、poetry、pdm)也可以交给 asdf 管理,实现"一条命令装齐环境"。

下一节

继续学习:uv 工具链管理

参考链接 ​