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
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
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
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
.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