Updating Python
MP 147: How do you clean up when you have too many Pythons lying around?
About a year and a half ago, I wrote a post about how to update the version of Python you're using locally. I was hoping that would be a guide I could refer back to, as I built a better habit of staying up to date. But shortly after that post came out, I started using uv to manage my local Python versions. I've spent the last year and a half using uv
to build virtual environments, but now I have a mix of uv
- and pyenv-based Python versions lying around. I want to simplify things, and just use uv
from this point forward.
I imagine I'm not the only one with a number of outdated Python versions lying around, so I'm going to write up my experience cleaning up all the Pythons on my system. If you want to clean up your system as well you probably won't follow these exact steps, but your overall process will probably be somewhat similar to this.
What do I want?
Before removing or installing anything, I want to re-evaluate my needs for local versions of Python:
- I write a bunch of small scripts that don't use any third-party libraries, and don't need a dedicated virtual environment. I want to have a
python
command on my system that always points to the latest point release of the most recent stable version of Python. At the time of this writing, that's Python 3.13.5. - I want to easily be able to create virtual environments based on any version of Python I might need, from older versions like Python 3.8 to release candidates like Python 3.14.0rc1.
- I may want to have commands like
python3.12
,python3.11
, and maybe a few other versions available on my system. I test programs on these older versions regularly enough, that it might be nice not to have to create virtual environments every time I want to run one of these versions.
If you're cleaning up your system, take a moment to figure out what you actually need in your local Python versions these days. My needs have definitely changed over the years, in part due to the kinds of work I'm involved in, and in part due to the changes in tooling. My work has become more varied and complex, but the tools for managing different Python versions have gotten much better. So, I should end up with a simpler setup even though my needs have gotten more complicated.
It's also a good to check out Python's status page, and see what the latest point releases are for each major release:

At this point, Python 3.9 is just about to reach end of life, and Python 3.14 is almost ready for its initial release.
What do I have?
Before making any changes, I want to take a quick inventory of what I have on my system. I'm pretty sure I've only used pyenv
or uv
to install Python on this system, so I should be able to use those tools to see what I have.
First, let's see what my current python
command points to:
$ python -V Python 3.12.8 $ which python /Users/eric/.pyenv/shims/python
The -V
flag shows the current version, and the which
command shows the path to the actual python
executable. Here I can see my main python
command is pointing to 3.12.8, and it's managed by pyenv
. I'm going to get rid of this, because I don't want anything related to pyenv
on my system at this point.
Note: To be clear, I haven't had any problems with pyenv
. I have just really enjoyed using uv
, and it does everything I was using pyenv
for, in a simpler workflow.
Now let's see what else I used pyenv
to install:
$ pyenv versions system 3.11.8 * 3.12.8 (set by /Users/eric/.pyenv/version) 3.13.1
I'm going to make sure all these, except system
, are removed.
I think I might have used homebrew to install Python at some point. Let's check that out:
$ brew list | grep python python-packaging python@3.11 python@3.13
I seem to have used brew
to install Python 3.11 and 3.13 at some point. I'll make sure to remove those as well.
Let's make sure I know where the system Python is. I really don't want to remove that, so verifying its current location is probably a good idea.
$ /usr/bin/python3 -V Python 3.9.6
This is the usual path to the system Python on modern versions of macOS, and it's there on my system. That's good.
Cleaning up
I might have some more Python installations lying around, but I'm going to start cleaning up my system before looking for anything else. I know I don't want pyenv
anymore, so I'm going to get rid of that first.
Removing pyenv
I think you can just uninstall pyenv
and that should get rid of the versions it installed, but I'm going to do a little more work and uninstall those versions myself first:
$ pyenv versions system 3.11.8 * 3.12.8 (set by /Users/eric/.pyenv/version) 3.13.1 $ pyenv uninstall 3.11.8 3.12.8 3.13.1 pyenv: remove /Users/eric/.pyenv/versions/3.11.8? (y/N) y pyenv: 3.11.8 uninstalled pyenv: remove /Users/eric/.pyenv/versions/3.12.8? (y/N) y pyenv: 3.12.8 uninstalled pyenv: remove /Users/eric/.pyenv/versions/3.13.1? (y/N) y pyenv: 3.13.1 uninstalled
With pyenv
, you can uninstall multiple versions of Python in one command. pyenv
will confirm the removal of each version.
Now I shouldn't see any pyenv
versions recognized on my system:
$ pyenv versions system
Okay! That looks good.
Now I'm going to follow the official directions for uninstalling pyenv
`. For me, that meant removing some pyenv
-specific lines from my ~/.zshrc
file. I also needed to remove the root pyenv
directory:
~$ ls -alh | grep pyenv drwxr-xr-x 6 eric staff 192B Nov 14 2024 .pyenv ~$ ls -alh .pyenv total 24 drwxr-xr-x 2 eric staff 64B Jul 31 21:32 shims -rw-r--r-- 1 eric staff 7B Jan 2 2025 version drwxr-xr-x 3 eric staff 96B Jul 31 21:32 versions
The only pyenv
-related directory in my home folder is .pyenv, and it has three subdirectories. pyenv
uses shims to manage which python
commands point to which Python executables. Removing the .pyenv directory should remove all remaining pyenv
-specific files from my system.
~$ rm -rf .pyenv ~$
And now I'll use homebrew to uninstall pyenv
:
$ brew uninstall pyenv Uninstalling /opt/homebrew/Cellar/pyenv/2.6.3... (1,333 files, 4.2MB)
Now let's try my python
command again:
$ python -V zsh: command not found: python
Okay, that's good, because python
was pointing to an interpreter installed by pyenv
. But it's a little scary to think I might not have python
pointing to anything. There are some system utilities on macOS that depend on having a system Python installed. I think this is just an alias issue, so let's make sure the system Python is still there:
$ /usr/bin/python3 -V Python 3.9.6
Okay, it's still there. I think the system utilities all use the full path to the interpreter, so I don't think I'm going to see any issues from not having the python
command point to anything at the moment.
Removing homebrew Pythons
Now let's remove those homebrew Python versions:
$ brew list | grep python python-packaging python@3.11 python@3.13 ~$ brew uninstall python@3.11 Uninstalling /opt/homebrew/Cellar/python@3.11/3.11.13... (3,306 files, 62.1MB) ~$ brew uninstall python@3.13 Error: Refusing to uninstall /opt/homebrew/Cellar/python@3.13/3.13.5 because it is required by lilypond and pipx, which are currently installed. You can override this and force removal with: brew uninstall --ignore-dependencies python@3.13
That's interesting. I was able to remove 3.11 without any issues. But I have two packages installed by homebrew that seem to depend on a homebrew-supplied version of Python 3.13. I might be able to point those packages to a uv
-supplied Python, but I don't want to get into that now. I'm okay with having one up-to-date Python interpreter on my system provided by homebrew.
Installing Python with uv
Now I want to install an up to date version of Python with uv
, and use that as my default interpreter for general Python work.
First, let's update uv
:
$ brew upgrade uv ==> Upgrading uv 0.7.12 -> 0.8.4
Now I can install the latest version of Python:
$ uv python install Installed Python 3.13.5 in 930ms + cpython-3.13.5-macos-aarch64-none (python3.13)
If you don't specify a version, uv
will install the latest stable release. (Note that it's uv python install
, not uv install python
!)
But, my python
commands are still blank, or pointing to brew-installed interpreters:
$ which python python not found $ which python3 /opt/homebrew/bin/python3 $ which python3.13 /opt/homebrew/bin/python3.13
You can find the location of your uv
-managed Python interpreters with the dir
command:
$ uv python dir /Users/eric/.local/share/uv/python
I want to point my python
command to the interpreter that uv
just installed. That should be a one-line alias in my .zshrc file:
alias python="$HOME/.local/share/uv/python/cpython-3.13.5-macos-aarch64-none/bin/python3.13"
Now my python
command is working again, and it points to the uv
-installed Python:
$ python -V Python 3.13.5 ~$ which python python: aliased to /.../uv/.../cpython-3.13.5-.../bin/python3.13
I ended up removing this alias a short time later, after finding that it conflicted with the python
command in active virtual environments. We'll get to that in a moment.
More uv
pythons!
Now I can give myself a bunch of interpreters:
$ uv python install 3.12 3.11 3.10 3.9 Installed 4 versions in 1.64s + cpython-3.9.23-macos-aarch64-none (python3.9) + cpython-3.10.18-macos-aarch64-none (python3.10) + cpython-3.11.13-macos-aarch64-none (python3.11) + cpython-3.12.11-macos-aarch64-none (python3.12)
I don't need to make aliases for these, because uv
is on my path and it doesn't conflict with any brew-installed Python versions. So now I can call any interpreter I want:
$ python3.13 -V Python 3.13.5 $ python3.12 -V Python 3.12.11 $ python3.11 -V Python 3.11.13 $ python3.10 -V Python 3.10.18 $ python3.9 -V Python 3.9.23
And with those installed, why not go ahead and install the pre-release version of 3.14 as well:
$ uv python install 3.14 Installed Python 3.14.0rc1 in 923ms + cpython-3.14.0rc1-macos-aarch64-none (python3.14) $ python3.14 -V Python 3.14.0rc1
This is great! I can run any Python script outside a virtual environment with any version of Python I want, and I can start a terminal session using any of these interpreters as well. I just spent a couple days doing a close reading of all the Python release notes for versions 3.11 through 3.14. Being able to jump into any of these interpreters is a great way to explore the differences between recent versions of Python.
Virtual environments are easy with uv
Just to be clear, it should be easy to create a virtual environment with uv
, using any version of Python you want. For example, let's say I wanted to see a bug that a user reported using Python 3.12.8.
Here's how to make a virtual environment with any point release of Python, even if you haven't already installed that version:
$ uv venv .venv --python=3.12.8 Using CPython 3.12.8 Creating virtual environment at: .venv Activate with: source .venv/bin/activate $ source .venv/bin/activate (.venv)$ python -V Python 3.13.5
The --python
flag lets you specify any Python you want when creating a new virtual environment. But this isn't quite working here. I can see that 3.12.8 is being used to create the virtual environment. But when I activate the environment and use the python
command, I'm getting 3.13.5. I'm pretty sure that's happening because of the python
alias I created earlier.
Let's check:
(.venv)$ which python python: aliased to /.../uv/python/cpython-3.13.5-.../bin/python3.13
Yes, that's the issue. I could do something to make sure that alias only takes effect outside virtual environments. But I probably don't need a general python
command on my system anymore. With all those easily-accessible python3.x
commands, I should just name the specific version I want to use any time I run a script outside a virtual environment.
This also has the advantage that I'll always know which version of Python I'm using. I should be able to completely avoid those annoying situations where you're accidentally using a different version than what you thought you were. From now on, python
should only ever point to an interpreter in an active virtual environment. If I ever use python
outside a virtual environment, I'll get an error and either use a more specific python3.x
command, or activate the environment I meant to use.
After removing the python
alias from ~/.zshrc, I can make a 3.12.8 environment that works as it should:
$ rm -rf .venv $ uv venv .venv --python=3.12.8 Using CPython 3.12.8 Creating virtual environment at: .venv Activate with: source .venv/bin/activate $ source .venv/bin/activate (.venv)$ python -V Python 3.12.8
If I need to use python
in a terminal, ie for teaching purposes, I can always use a temporary alias:
$ alias python=python3.13 $ python -V Python 3.13.5
This alias will only last as long as that terminal window is open, and I can unset it or point it to a different interpreter any time I need.
More consistent updates
One of my long-term goals has been to more consistently update my Python versions as new point releases come out. uv
makes that much easier; take a look at their documentation about upgrading installed versions.
Since I'm using uv
to manage all my interpreters, I can now upgrade every installed version with one command:
$ uv python upgrade All versions already on latest supported patch release
This is incredibly helpful, and it's part of why people are so enthusiastic about uv
, and the work that Astral is doing in general. We want everyone to be on secure, well-supported versions of Python, and tools like this make it much easier for everyone to do so.
That said, I will review uv
's upgrading documentation again before deciding how to upgrade. If you read that documentation carefully, there are a couple ways you can handle how the older point releases are treated during upgrades. As of this writing, the default behavior is to keep the old versions installed, so that virtual environments pointing to those interpreters are still functional. I don't want a bunch of old interpreters piling up, and I don't mind rebuilding virtual environments. So I'll probably delete all existing Python interpreters and reinstall them, or use a command that replaces the old point releases instead of leaving them in place.
All these approaches are much simpler than they used to be, so I'm actually starting to look forward to the next upgrades! At any point, you can see all your installed versions with the list
command:
$ uv python list --only-installed
This actually showed me a few more older point releases I had laying around. I cleaned those up, so I'm moving forward with only one interpreter per Python version.
For the record, I love grep
; here's a quick command that shows only the uv
-installed interpreters on my system, without any extra lines showing redundant links to this core set of interpreters:
$ uv python list --only-installed | grep uv/python | grep -v local/bin /.../uv/python/cpython-3.14.0rc1-macos-aarch64-none/bin/python3.14 /.../uv/python/cpython-3.13.5-macos-aarch64-none/bin/python3.13 /.../uv/python/cpython-3.12.11-macos-aarch64-none/bin/python3.12 /.../uv/python/cpython-3.11.13-macos-aarch64-none/bin/python3.11 /.../uv/python/cpython-3.10.18-macos-aarch64-none/bin/python3.10 /.../uv/python/cpython-3.9.23-macos-aarch64-none/bin/python3.9
I can scan this output, see exactly how many versions I currently have installed, and see which point release each version is at as well.
I won't be surprised to find other cruft from various ways of installing Python on my system over the years. I'll remove the unused cruft as I find it, but I'm also not too worried about those remnants interfering with my current setup. The system Python is pretty well isolated, and the ones I want to use can be updated or removed at any point, using fairly straightforward uv
commands. Also, this post is a great reference when I need to clean up my system again. Most Python writers I know are writing partly for an external audience, but also to have a clear record of their own work.
Conclusions
At this point, I have a simpler but more flexible Python environment on my system. Every interpreter is installed by uv
. Each interpreter is available either through a version-specific command such as python3.13
, or in an active virtual environment through the command python
.
If your system has accumulated a bunch of Python cruft, consider whether you can move to a uv
-managed approach as well. If you're just getting started with Python and looking for a good way to manage your environment, I highly recommend starting with uv
. Even if you're not using uv
, it's probably a good idea to reevaluate how you manage your environment from time to time, update your workflows, and clean out the most obvious cruft from your system.