Using multiple versions of Python on macOS using pyenv and a series of ugly hacks.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *