macOS (at least, as of Big Sur 11.2.1) ships with Python 2.7.16 by default. This seems curious when you consider that Python 2.7 was deprecated and no longer supported after December 2020, but there are Reasons for shipping the newest and shiniest macOS operating system with a (much) older version of Python.
For one thing, Python 3 isn’t backward compatible, and Python 2 has vast library support that might be critical to your workflow, and not having those libraries available seems like a recipe for disaster. For another thing, you invoke Python 2 with the python
command, and Python 3 with the python3
command – and while this seems less obviously an issue it’s actually a much bigger problem that you’d expect. After all, how many pieces of code or scripts are out there, undocumented, starting with #!/usr/bin/python
? I’m betting the answer to that question is, well, a lot.
So, what to do if you want to be able to run both Python 2.7 and Python 3 on a single install of macOS? Well, there’s a two-pronged way of doing it by utilizing the homebrew pyenv
package (which is ingenious and well-supported) along with a little .zshrc
hack (which is something I came up with and therefore almost certainly deeply problematic).
You can find pyenv
here, and the simple installer for it linked here. In the simplest terms, what pyenv
does is intercept any request that’s made for python commands and then slides those requests over to the selected version of python. It’s extremely clever and additionally that rare and unusual creature in that it’s a GitHub project that’s both well-maintained and well-documented. You’ll need to install the Xcode Command-Line tools with an xcode-select --install
command, but once that’s done you can run the installer linked above and it shouldn’t cause you much trouble.
Once installed, there are a couple of additional required steps. Firstly, you’ll need to make a backup of the .zshrc
file in your home directory with ditto ~/.zshrc ~/.zshrcbackup
(if you don’t already have a .zshrc
file then feel free to skip this part.)
Next, make another copy of your .zshrc
file: ditto ~/.zshrc ~/.zshrc2
(This may seem needlessly duplicative, but it’ll come in handy in the ugly hack part of the procedure later on).
Finally, edit your .zshrc
file and add these lines onto the end:
export PATH="/Users/daveb/.pyenv/bin:$PATH"
(Note: substitute your short username for daveb
unless your name is also Dave B.)
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
Once you’ve done all the above, quit Terminal, open it again, then install your Python version of choice by issuing the pyenv
command, invoking the install
option, and then adding the version of Python you want to install like so:
pyenv install 3.9.1
…and that’s it. If you want to install other versions of Python then you can run the pyenv install
command again with the relevant build, and if there’s one particular build that you want to, say, specify Python 3.9.1 as the default then you can make that so with the pyenv global 3.9.1
command. A more complete and in-depth list of commands can be found here.
So, now that’s all the pretty, elegant stuff out of the way, so time to move on to the ugly hack. The problem I’ve found with pyenv
is that it’s great for installing versions of Python that are up-to-date, but that it really doesn’t seem to want to toggle back to the version that Big Sur ships with, and there doesn’t seem to be a convenient off switch for the thing. Further, trying to install that old version (2.7.16) doesn’t work because Python 2.7.16 is ancient and wicked and pyenv
refuses to allow something so arcane to be installed on your machine (despite the fact that it was on there already).
To get around that, I added this to my .zprofile
:
alias py='mv ~/.zshrc ~/.zshrctemp; mv ~/.zshrc2 ~/.zshrc; mv ~/.zshrctemp ~/.zshrc2'
Ugh. Just look at that thing.
Like I said, it’s ugly, but when you type the alias py
into a terminal window then what happens next is that it takes your existing .zshrc
file, renames it to .zshrctemp
, then takes the .zshrc2
file and renames it .zshrc
, and then finally takes the .zshrctemp
file and rename it .zshrc2
.
In effect, it takes the .zshrc2
file (the one that isn’t set up to enable pyenv
) and switches it out with your .zshrc
file (the one that is set up to enable pyenv
). When .zshrc2
becomes .zshrc
, pyenv suddenly no longer works, and your computer defaults to the version of python that it shipped with (in my case 2.7.16) because it suddenly doesn’t know any better. And because the command just switches those two files around, issuing it again turns the tables and re-enables pyenv
.