How to Update Python Version: The Better Way

We’ve all been there — there comes a time when we must update our Python version to meet a different Python version yet, be it at work or when working on personal projects.

Just a quick search about “update python version” and we will be bombarded with suggestions to run python --version followed by brew upgrade python3 or sudo apt-get update.

Okay cool. Problem solved right?

Not really. Probably 9/10 times an upgrade won’t cut it — enter another project. And guess what? It wants a different Python version, maybe an older one just to make our life a little bit more miserable.

So, here we are, stuck between a rock and a hard place, asking ourselves, "Do I downgrade Python now? But what if I need to juggle both projects? I didn't sign up for this symlink or PATH variable wrestling match!

Let's use pyenv?

We know it’s not uncommon either to find ourselves in another project needing a different Python version yet.

Now, with some quick search, you can tell most Python folks swear by pyenv (docs) for managing Python versions.

💡
Tip on a quick search: How to Google With a Bang!

Don’t get me wrong, it works but it’s just not for me.

Well, given that I work with a lot of CLI tools like go, node, terraform, git, etc., I prefer the simplicity of using a single tool – asdf.

In other words, I very much prefer to use asdf to manage all my programming language or CLI tool versions, rather than dealing with the likes of gvm, nvm, and pyenv separately.

asdf Python Quick Guide

Beyond Python, asdf supports various plugins. But, let's focus on Python without delving into exhaustive details.

💡
Hint: run asdf plugin list all to list all available plugins.

Installation

Easy, just follow asdf-vm.com/guide/getting-started.html based on your system specifications:

  1. OS (e.g. linux, macOS)
  2. Package manager (e.g. brew, apt, pacman, etc.)
  3. Shell (e.g. zsh, bash, etc.)

For instance, on macOS with Homebrew and ZSH:

# Using Homebrew on macOS
brew install asdf
echo -e "\n. $(brew --prefix asdf)/libexec/asdf.sh" >> ${ZDOTDIR:-~}/.zshrc

Setup

Add the Python plugin:

# asdf plugin add <name>: Adds a plugin for managing a specific runtime
# e.g. <name>: python, nodejs, golang
asdf plugin add python

Basic Usage

Let’s install our first Python version:

# asdf install <name> <version>: Installs a specific version of a runtime
asdf install python 3.12.1

What if most of your projects rely on Python 3.12.1? Well, let’s set Python 3.12.1 as our global/default Python version:

# asdf global <name> <version>: Sets a global (default) version of a runtime
asdf global python 3.12.1
cd && python --version # Python 3.12.1

Next, let’s install more Python versions!

asdf install python 3.8.13
asdf install python 3.9.16
asdf install python 3.10.9
asdf install python 3.11.3

Wait, I lost track, how many different Python versions have I installed…?

asdf list python
#  3.10.9
#  3.11.3
# *3.12.1
#  3.8.13
#  3.9.16

"*3.12.1" in the asdf list python output indicates that 3.12.1 is the currently active (local) Python version in the current directory.

Now, you can easily switch between different Python versions:

# Go to my project
cd ~/github.com/ngshiheng/burplist

# Project current Python version
python --version # Python 3.12.1

# But, I need Python 3.8.13
asdf local python 3.8.13

# Yay!
python --version # Python 3.8.13

That’s it! You'll likely find yourself using this set of commands about 80% of the time.

Cheatsheet

Here's a quick refresher:

# "How to install a specific Python version?"
asdf install python 3.12.1

# "What versions have I installed?"
asdf list python

# Set version on global level
asdf global python 3.12.1

# Set version on project level
asdf local python 3.12.1

# "What is my current Python version in this dir?"
python --version

.tools-version

Now you may notice that your project directory may contain a file called .tool-versions. It's used to remember which versions of these tools each project needs (reference).

Should I commit this file to Git?

If having the .tool-versions file in your project helps everyone on your team use the same versions of tools, then it's a good idea to include it in your source control. It keeps things consistent for everyone.

Not Just Python

This approach isn't limited to Python; it works for managing versions of other tools like Node.js, Go, etc. All you need to do is to replace "python" with the respective tool/plugin name:

# Same examples, but in golang:
asdf plugin add golang
asdf install golang 1.21.6
asdf list golang
asdf global golang 1.21.6
asdf local golang 1.21.6
go version

# Same examples, but in nodejs:
asdf plugin add nodejs
asdf install nodejs 21.6.1
asdf list nodejs
asdf global nodejs 21.6.1
asdf local nodejs 21.6.1
node --version

Closing Thought

These days, when considering adopting a new tool, I've adopted a systematic approach:

  1. Firstly, I check asdf to see if there's plugin support available (asdf plugin list all). Vet the plugin first!
  2. If not, I explore whether the specific CLI tool has its own version manager like gvm, nvm, rubyenv, pyenv, tfenv, etc.
  3. If neither option is viable, then only I resort to installing the tool from the source via my package manager like brew or apt

Following this decision-making chain has significantly simplified version management for all my tools, saving me considerable time and pain.

References

P/S: A friend recommended an alternative to asdf called mise. I haven't had the chance to check it out yet, but it seems promising. I might give it a try in the near future.

💬
Update (19 Nov 2024): I recently made the switch to mise (pronounced as "MEEZ"), and I have to say, I'm really impressed! The UX feels much better compared to asdf. Over the years, I still struggle to remember the commands for asdf, but I don’t seem to have that problem with mise at all. The author of mise mentioned that asdf has quite a few quirky UX papercuts, and I can definitely relate to that! That said, I still think asdf still has its place. It's still the most popular version manager out there.
Written by human. Hosted on Digital Ocean.