Python Version Management (asdf, mise, pyenv)
In 1956, Malcolm McLean revolutionized global trade — he standardized shipping containers, transforming chaotic cargo handling into efficient transport for diverse goods without unpacking entire loads.
We encounter a parallel challenge managing Python versions: projects often require specific releases (3.8 for legacy support, 3.12 for new features), but without tools, switching means manual installs, conflicts with system Python, or rebuilding venvs repeatedly. Version managers like pyenv, asdf, and mise standardize this — installing side-by-side, switching seamlessly via shims, while you maintain project isolation.
Of course, virtual environments help with packages, though they don’t address core interpreter versions. Before diving in, let’s consider why these tools matter for your workflow.
Why Use a Python Version Manager?
Python projects frequently demand precise versions — a Django app might need 3.10, while a new ML tool requires 3.12. Your system Python (often outdated or privileged) conflicts here, and while virtualenvs isolate packages effectively, they rely on a fixed interpreter.
Without a version manager, you resort to manual Homebrew/apt installs, Docker hacks, or conda — each with trade-offs: manual installs pollute your system, Docker adds overhead for simple scripts, conda pulls in non-Python deps. Version managers address this directly:
- Install multiple interpreters side-by-side without conflicts.
- Switch via global, local (.python-version), or shell settings.
- Provide shims so
pythonresolves correctly automatically. - Often integrate with venvs/pip for full environments.
We’ll examine pyenv (Python-focused), asdf (multi-language), and mise (modern/fast) — though before that, note these tools build from source (slow) or use prebuilts, with varying plugin ecosystems. You can experiment with python --version mismatches to see the pain firsthand.
pyenv: Python-Focused Approach
pyenv originated as a Python port of rbenv — a lightweight Ruby version manager that avoids RVM’s heavier shell modifications. Since 2012, it has become the reference Python tool, though it compiles from source (requiring build deps) and focuses solely on Python.
You might already know tools like nvm for Node; pyenv follows similar shimming philosophy — transparent version switching without PATH hacks.
Installation
First, the one-liner handles most setup:
$ curl https://pyenv.run | bash
Add to your shell config (~/.bashrc or ~/.zshrc):
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
macOS users can brew install pyenv instead — though ensure Xcode Command Line Tools via xcode-select --install.
Dependencies matter: Builds fail without them. On Ubuntu/Debian:
$ sudo apt update
$ sudo apt install -y make build-essential libssl-dev zlib1g-dev \
libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \
libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev \
libffi-dev liblzma-dev
Restart shell. Verify:
$ pyenv --version
pyenv 2.4.3 (or similar)
Basic Usage Walkthrough
List versions:
$ pyenv install --list | grep 3.12
3.12.7
3.12.6
...
Install and set:
$ pyenv install 3.12.2 # Takes 5-15 min depending on deps
$ pyenv versions # Shows installed
system
* 3.12.2 (set by /Users/you/.pyenv/version)
$ pyenv global 3.12.2
$ python --version
Python 3.12.2
For projects: cd myproject; pyenv local 3.11.0 creates .python-version.
With pyenv-virtualenv plugin (pyenv install pyenv-virtualenv), pyenv virtualenv 3.12.2 myenv.
Trade-offs: pyenv’s simplicity shines for Python-only workflows — lightweight shims, per-project files. However, source builds are slow (no prebuilts), limited to Python (no Node/Rust), and plugin-dependent for extras like virtualenvs. If you need multi-language, consider asdf — though pyenv remains mature and battle-tested.
asdf: Multi-Language Plugin System
asdf-vm emerged around 2017 as a plugin-based manager for multiple runtimes — inspired by tools like tfenv (Terraform) but extensible via Git repos. The asdf-python community plugin handles Python, maintaining compatibility with .tool-versions.
If you’ve used direnv or chezmoi, asdf’s plugin model feels familiar — decentralized, community-driven.
Installation
Clone core:
$ git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.1
Source in shell (~/.bashrc):
. $HOME/.asdf/asdf.sh
. $HOME/.asdf/completions/asdf.bash # Optional
Reload shell. Verify:
$ asdf --version
v0.14.1
Python Setup & Usage Walkthrough
Add plugin:
$ asdf plugin add python https://github.com/asdf-community/asdf-python.git
$ asdf plugin list python
python git@github.com:asdf-community/asdf-python.git latest
Install/use:
$ asdf list all python | grep 3.12 | tail -5
3.12.7
3.12.6
...
$ asdf install python 3.12.2 # Builds, needs pyenv-like deps
$ asdf list python
3.12.2
$ asdf global python 3.12.2
$ python --version
Python 3.12.2
Per-project: .tool-versions with python 3.12.2.
After pip: asdf reshim python updates shims for new bins.
Trade-offs: asdf unifies workflows across languages (Node, Elixir, etc.) via plugins — ideal for polyglot repos. However, each plugin builds from source (slow, dep-heavy), less optimized for Python alone than pyenv, and plugin quality varies. Mise offers similar multi-lang but faster.
mise: Rust-Based Modern Manager
mise (rebranded from rtx in 2023) builds on asdf’s philosophy but in Rust for performance — prebuilt binaries skip compilation, and it adds tasks/Envs. Asdf-compatible .tool-versions eases migration.
Like asdf but faster, it suits users tired of build times.
Installation
Simple script:
$ curl https://mise.run | sh
Activate (per-shell):
$ echo 'eval "$(mise activate zsh)"' >> ~/.zshrc # bash/fish/nu/etc.
Reload. Verify:
$ mise --version
mise 2024.9.8 (or latest)
No build deps needed!
Usage Walkthrough
Auto-install/use:
$ mise list remotes python | grep 3.12 | tail -3
3.12.7
3.12.6
3.12.5
$ mise use python@3.12.2 # Downloads prebuilt
$ mise current # Shows active
python 3.12.2
$ python --version
Python 3.12.2
Local: mise use --local python@3.11 creates .tool-versions or .mise.toml.
Tasks: .mise.toml with [tasks.test] cmd = "pytest".
mise doctor checks setup; integrates uv/pipx.
Trade-offs: mise excels in speed (prebuilts, Rust), multi-lang support, extras (tasks, settings sync). Newer (~2021 origins), so ecosystem smaller; advanced features like tasks have a curve. For Python-only, pyenv simpler; asdf users migrate easily.
Comparison Table
| Aspect | pyenv | asdf | mise |
|---|---|---|---|
| Languages | Python only | Multi (plugins) | Multi (prebuilt focus) |
| Install Method | Source build | Source build (plugins) | Prebuilt binaries |
| Speed | Slow (~10min/build) | Slow (per-plugin) | Fast (~30s/download) |
| Config | .python-version | .tool-versions | .tool-versions/.mise.toml |
| Integration | Shims, plugins | Shims, plugins | Shims + activate/tasks |
| Python Maturity | High (10+ years) | Good (community) | Good (recent, active) |
| Deps Needed | Build tools | Build tools | None |
pyenv prioritizes minimalism — shims without shell wrappers. asdf extends to polyglot via plugins, though decentralized maintenance varies. mise evolves asdf with Rust efficiency and built-ins (no plugins for core langs).
Decision Factors: Python-only/simple? pyenv. Multi-lang existing asdf? Stick or migrate to mise. Speed/long-term? mise — though test in your env (mise doctor, pyenv doctor).
Which to Choose?
Consider your context:
- Python-centric, prefer simplicity: pyenv — lightweight, no multi-lang overhead.
- Polyglot repos (Node/Rust/etc.): mise for speed; asdf if plugin ecosystem needed.
- asdf migrate: Seamless via
.tool-versions; mise adds prebuilts/tasks without breaking changes. - Constraints: No build deps? mise. Legacy plugins? asdf.
Long-term: All mature, though mise’s Rust base suggests better sustainability. Test locally — pyenv doctor, asdf current, mise doctor.
Common Questions
pyenv vs mise? pyenv for Python isolation; mise if multi-lang or avoiding builds — though mise handles Python equivalently.
Migrating from conda/brew? These pollute system; version managers isolate better, integrate venvs.
asdf deprecated? No — actively maintained, though mise positions as faster successor with asdf compat.
Virtualenvs with these? Yes: pyenv-virtualenv plugin, asdf + venv, mise + uv/pipx.
Experiment: Start with mise (curl https://mise.run | sh), fallback if needed — though most find it covers cases.
Sponsored by Durable Programming
Need help maintaining or upgrading your Python application? Durable Programming specializes in keeping Python apps secure, performant, and up-to-date.
Hire Durable Programming