python brains

This is a simple guide to set up Python in macOS / Linux. The goal is to be able to manage projects that require different Python versions (say, 3.10 vs 3.6), and that have different, potentially conflicting dependencies.

Contrary to the Zen of Python, there are a myriad of ways to achieve this very reasonable goal (see this SO question for instance). This is my way, which is nevertheless based on much testing and assessment of the pros and cons of each method.

Getting Python

Get Homebrew (macOS)

We’ll use pyenv to install Python, but in order to get pyenv itself I suggest using a package manager. If you’re on Linux, I trust you know which package manager you have. In macOS I always install Homebrew.

  1. First you’ll need Xcode. If you don’t know what Xcode is that’s great, because then you can get away with installing a minimal version. Run
    xcode-select --install
  2. Installing Homebrew boils down to running
    /bin/bash -c "$(curl -fsSL"
  3. I suggest that right after installing Homebrew, you make sure all package definitions (a.k.a. formulae) are up-to-date, which can be done with
    brew update

Install pyenv

I like using pyenv to manage multiple Python versions, as it sticks to the Unix philosophy of doing one thing well.

  1. Before installing pyenv itself, install its appropriate Python build dependencies. In macOS, run
    brew install openssl readline sqlite3 xz zlib tcl-tk
  2. Then, install pyenv with
    brew install pyenv

Add pyenv to path

The pyenv program we just installed needs to be added to the system’s path so that it runs the appropriate version of Python whenever we type python (more details here).

  1. Find out which shell you’re using with

    echo $0

    In macOS you should get either bash or zsh. Modern macOS versions should have zsh (see screenshot below).


  2. Finally, we’ll set up our shell environment for pyenv. Assuming your shell is zsh, then run

    echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc
    echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc
    echo 'eval "$(pyenv init -)"' >> ~/.zshrc

    Note: if your shell is bash, then replace ~/.zshrc with ~/.bashrc in the lines above.

Install Python with pyenv

pyenv makes it easy to install any Python version that we want (for example, 3.6.4 or 3.12.0). Moreover, it contains different Python distributions (for example, Anaconda 3 2022.05). The full list of available installs can be obtained with

pyenv install --list

You’ll get a long list of options. If you know which version you require (say, for a pre-existing project that contains a requirements.txt file), you can install a specific version with pyenv install <version>. For example,

pyenv install 3.10.9    # just an example

If you are unsure about which version to install, I’d just go with the most recent one. The latest available version can be printed out and installed using the two following commands, respectively:

# print latest version:
pyenv install --list | sed 's/^  //' | grep '^\d' | grep --invert-match 'dev\|a\|b' | tail -1
# install latest version:
pyenv install $(pyenv install --list | sed 's/^  //' | grep '^\d' | grep --invert-match 'dev\|a\|b' | tail -1)

If you know which version of Python you want (say, 3.9), then you can omit the revision and pyenv will resolve the request to the latest available revision:

pyenv install 3.9   # installs 3.9.16

Keep in mind that you can always add or remove versions, so this decision is not final. To uninstall a version, simply use pyenv uninstall <version>. See here for more info.

Set up a global Python version

Now that Python 3 is installed via pyenv, we need to set it as a global (i.e. system default) version. You can check out the available Python versions in your machine with

pyenv versions


The output I get after running this command in my local machine is above. Notice that there is an asterisk next to 3.8.6: this indicates it’s the active version, which in this case, it’s the one I’ve set up as my global Python version. This can also be obtained by running pyenv version (singular).

We can change the global default version by running pyenv global <version>, where <version> is any of the ones listed when running pyenv versions. For example, I can switch my global default version to 3.6.13 by running

pyenv global 3.6.13    # just an example

I can check that this worked by running pyenv version again, or simply by initializing the Python REPL with python. See output below.


If you only need one Python version for all your projects, and you’d prefer it wasn’t the system default (as you should), then having a single global version should work just fine. Up next, we’ll walk through how to set up projects that need different Python versions and environments.

Project-specific Python environments

In many instances we want to be able to create project-specific Python environments for our projects. That is, we want to

  1. use a specific Python version (say, 3.9.16), and
  2. have project-specific dependencies (libraries).

In order to fix ideas, suppose we want to create a new project ("foo") located in ~/foo.

Local Python version

Defining a local Python version enables easy switching to the Python required by a particular project. We can define a local Python version using pyenv local <version> inside the project’s root directory:

cd ~/foo
pyenv local 3.9.16

This will create a .python-version file inside ~/foo, and every Python call issued inside foo will use the version defined here, taking precedence over the global version. IDEs like VSCode will automatically detect this file and set the Python version accordingly.

Virtual environments

Virtual environments permit project-specific dependencies. That is, a virtual environment allow us to install libraries which are tied to a specific project. This is great because different projects might have different, potentially conflicting dependencies.

We can create a virtual environment for our foo project by running

cd ~/foo
python -m venv .venv

This will create a virtual environment inside ~/foo, which is really just a directory named .venv (or anything else, really) where the project libraries are installed. Because of this, we can delete a virtual environment by simply removing this directory (rm -rf .venv; be careful with rm -rf!).

Finally, we need to activate the virtual environment prior to using it. In Unix, this is done with

cd ~/foo
source .venv/bin/activate