Sane Python environment Part 2, pyenv
Managing Python interpreters and versions with pyenv

pyenv is a command-line tool that enables you to manage different Python environments in your $HOME, in isolation from interpreters located in root-owned directory /usr, typically called "system" Python interpreters.

That is, it enables you to mess with your Python environment without breaking the Python environment of your operating system or of other users. The core ideas behind pyenv are similar to the ones behind nvm for JavaScript, or rbenv for Ruby.

Note: This tutorial is part of a series about Python environments. You can go back to the first part with this link: Sane Python environment Part 1, isolation.

Installation

In this tutorial, we will assume that you are using Ubuntu or macOS. If you are using macOS, you should make sure to install the Xcode Command Line Tools (xcode-select --install) and Homebrew.

Pre-requisites and pyenv installer

The installation of pyenv requires some dependencies, so please install those first:

  • Ubuntu: sudo apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev xz-utils tk-dev libffi-dev liblzma-dev python-openssl git
  • macOS: brew install openssl readline xz

As pyenv is independent of your OS Python, it does not use Python for installation, and relies on a Bash script instead. This Bash script (bin/pyenv-installer) is provided in the pyenv-installer repo. Install it using the following command:

  • curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash

Pitfall: DO NOT use your system package manager to install pyenv! Indeed, your package manager is tightly coupled to other components of your system and this goes against isolation. Moreover, your package manager usually writes files to sub-directories of root-owned directory /usr (e.g. /usr/local/Cellar with Homebrew on macOS), which will almost always cause permissions issues later. While a poweruser will be able to deal with potential regressions introduced by the system package manager, the ordinary user most likely won't. To be convinced that dealing with the OS package manager in Python is difficult, you can google site:stackoverflow.com brew python, which at time of writing returns 32'000 results.

Setting up shims for pyenv

After downloading pyenv and installing it to ~/.pyenv, you will need to add a few initialization lines to your Shell config file (e.g. ~/.zshrc or ~/.bashrc):

export PATH="$HOME/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

These initialization lines include a modification of your $PATH, which enforces the precedence of pyenv's bin directory of your $HOME over the other registered directories, which usually include the bin sub-directories of /usr such as /usr/local/bin. This "precedence trick" is called a shim and enables pyenv to intercept all Python-related commands such as pip or python.

After updating your Shell config, exit/logout from your terminal (e.g. with exit) and then open it again. Please note that it's not enough to just source your Shell config file in many cases!

Pitfall: If your Shell config files contain some lines with export PATH="SOME_DIR:$PATH" already, make sure the pyenv initialization lines are added afterwards, to make sure $HOME/.pyenv/bin takes precedence in your $PATH over the bin sub-directories of /usr (e.g. /usr/local/bin).

Using pyenv versions

After opening the new terminal, the pyenv command should be available, and you should be able to run pyenv versions. This command will show you all the Python environments installed on your computer. As no new environment was installed yet, you should just see the "system" Python of your OS:

➜ pyenv versions
* system (set by /home/pierre/.pyenv/version)

Usage

Installing a Python version

Before attempting to install a Python version, first check that your machine is ready for it:

➜  pyenv doctor
Cloning /home/pierre/.pyenv/plugins/pyenv-doctor/bin/.....
Installing python-pyenv-doctor...
Installed python-pyenv-doctor to /tmp/pyenv-doctor.20191103204543.28966/prefix

Congratulations! You are ready to build pythons!

If pyenv doctor fails, fix the missing installations that it found. Once pyenv doctor succeeds, we can check which Python versions are available for installation, and install one of them:

➜ pyenv install --list | grep 3.8
  3.8.0
  3.8-dev
  3.8.1
  miniconda-3.8.3
  miniconda3-3.8.3

➜ pyenv install 3.8.1
Downloading Python-3.8.1.tar.xz...
-> https://www.python.org/ftp/python/3.8.1/Python-3.8.1.tar.xz
Installing Python-3.8.1...
Installed Python-3.8.1 to /home/pierre/.pyenv/versions/3.8.1

# Make sure the shims are up-to-date
➜ pyenv rehash

➜ pyenv versions
* system (set by /home/pierre/.pyenv/version)
  3.8.1

As you can see, our new Python version was installed successfully in ~/.pyenv/versions. In the output of the pyenv versions command, the little star next to the version name indicates the current global version of Python, which is initially system. Thanks to pyenv, your system Python is still active and fully functional. It was not destroyed by your new Python 3.8.1 installation.

To make sure our shims are up-to-date, we also ran pyenv rehash after installing the interpreter. If you don't run pyenv rehash, your new interpreter might not be installed correctly, as the GitHub member "dangitall" commented on the project issue tracker. I personally encountered the same issue on on macOS, Ubuntu 18.04 and Ubuntu 19.04 with pyenv 1.2.15.

➜ python
Python 2.7.12 (default, Dec  4 2017, 14:50:18) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>

After screaming out of joy for your preserved system, you can switch to your new version of Python using the pyenv global command:

➜ pyenv global 3.8.1

➜ python
Python 3.8.1 (default, Jan  7 2020, 23:51:55)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

Pitfall: In case your Python version is not updated after running pyenv global, run pyenv rehash or exit and re-enter your terminal.

Switching betwen versions

You can then switch back and forth between your versions using the same command:

➜ pyenv global system

➜ python
Python 2.7.12 (default, Dec  4 2017, 14:50:18)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 

➜ pyenv global 3.8.1

➜ python
Python 3.8.1 (default, Jan  7 2020, 23:51:55)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

Finally, be aware that pyenv has the ability to select multiple global Pythons at the same time, even though it is not required for common development use cases. That is, you may see other tutorials on the Internet where there is more than one single star in the output of pyenv versions.

Pitfall: As you can see, the notions of global and system Python do not refer to the same thing in pyenv. Your system Python is the one distributed by your OS or package manager (located somewhere in /usr), while your global Python is just a pointer to the Python currently selected by pyenv. This pointer is most often pointing to some sub-directory of the .pyenv directory in your $HOME, but can also refer to the system Python. To avoid messing up your system Python, you should always have your global Python pointing to a version installed using pyenv. In our case, the global Python should be pointing to the version 3.8.1 that was installed earlier.

Fixing issues with the $PATH

If you run into issues with the $PATH manipulations of pyenv, these StackOverflow answers might help you:

Uninstallation

pyenv is self-contained, so it can be removed fairly easily if needed:

  1. Optional: in case you already installed pipx from the next part of the tutorial, make sure to uninstall it first before uninstalling pyenv.
  2. in your Shell config, remove the following:
    • export PATH=... line containing pyenv
    • pyenv init lines
  3. remove the pyenv directory:
    • rm -rf ~/.pyenv
  4. exit and re-open your terminal

Now that you are familiar with pyenv, you can learn about Python tooling isolation in Part 3 of the tutorial.