pyenv python version manager

Why using pyenv?

 System Python

By default python comes pre-installed within your operating system.

If you are a Mac or Linux user, you can see the “System Python” that comes installed on your operating system:

zsh> which python
/usr/bin/python

Note: this version of python is available to all users (as reflected by its location).

However, this might not be the version you need:

zsh> /usr/local/bin/python3 --version
Python 3.6.8

Another problem is that by running sudo pip install <your-package>, you will be installing the Python package globally. What about if another needs another version of the package e.g. a slightly older version of the package or if two projects requires two different versions because of breaking changes introduced in the newer version?

Last but not the least, some operating system relies heavily on Python to perform operations. Installing a new version of Python could seriously dampen your ability to use your OS.

Pyenv

The logical place to look for to solve all the problems inherent to System Python is pyenv.

Pyenv is a great tool for managing multiple Python versions that can coexists simultaneously on your OS. You can then easily switch between the installed versions and use virtual environments to manage Python packages associated with each Python versions.

Installation

You need to install the following dependencies:

brew install openssl readline sqlite3 xz zlib

Add them within the PATH (macOS):

echo 'export PATH="/usr/local/opt/openssl@3/bin:$PATH"' >> ~/.zshrc
echo 'export LDFLAGS="-L/usr/local/opt/openssl@3/lib"' >> ~/.zshrc
echo 'export CPPFLAGS="-I/usr/local/opt/openssl@3/include"' >> ~/.zshrc
echo 'export PKG_CONFIG_PATH="/usr/local/opt/openssl@3/lib/pkgconfig"' >> ~/.zshrc
echo 'export PATH="/usr/local/opt/sqlite/bin:$PATH"' >> ~/.zshrcc
echo 'export LDFLAGS="-L/usr/local/opt/sqlite/lib"' >> ~/.zshrc
echo 'export CPPFLAGS="-I/usr/local/opt/sqlite/include"' >> ~/.zshrc
echo 'export PKG_CONFIG_PATH="/usr/local/opt/sqlite/lib/pkgconfig"' >> ~/.zshrc
echo 'export LDFLAGS="-L/usr/local/opt/zlib/lib"' >> ~/.zshrc
echo 'export CPPFLAGS="-I/usr/local/opt/zlib/include"' >> ~/.zshrc
echo 'export PKG_CONFIG_PATH="/usr/local/opt/zlib/lib/pkgconfig"' >> ~/.zshrc

Note: Pyenv comes with a set of useful dependencies:

  1. pyenv: The actual pyenv application
  2. pyenv-virtualenv: Plugin for pyenv and virtual environments
  3. pyenv-update: Plugin for updating pyenv
  4. pyenv-doctor: Plugin to verify that pyenv and build dependencies are installed
  5. pyenv-which-ext: Plugin to automatically lookup system commands

Then, install pyenv using the pyenv-installer:

curl https://pyenv.run | bash

Restart the terminal for the PATH changes to be reflected:

exec $SHELL

Finally, check that everything did worked it:

zsh> pyenv -v
pyenv 2.3.19

Uninstall pyenv

On MacOS:

brew remove pyenv

Using pyenv

Install python versions

zsh> pyenv install --list
    3.6.2
    3.6.7
    3.7.2
    3.8.2
    3.9.12
    3.10.4
    3.11-dev
    3.11.4

All the installed version will be located in your pyenv root directory:

zsh> ls ~/.pyenv/versions/
3.10.6  3.11.4  3.6.15  3.6.8   3.6.9   3.8.16  3.8.17  3.9.9

Note: make sure to regularly pyenv update to have access to all the latest python versions.

Uninstall python versions

You can simply remove the versions from the pyenv root folder:

rm -rf ~/.pyenv/versions/3.10.6

or use the provided command:

pyenv uninstall 3.10.6

 Switching between Python versions

You can see the python version you have installed:

zsh> pyenv versions
* system (set by /Users/johndoe/.pyenv/version)
3.6.8
3.6.9
3.6.15
3.8.16
3.8.17
3.9.9
3.10.6
3.11.4

Note: the * indicated which version of python is currently active. By default, it is system python. You can confirm is using the which command:

zsh> which python3
/Users/johndoe/.pyenv/shims/python3

pyenv insert itself into the PATH. From the OS’s perspective, pyenv is the executable getting called when you execute which python3. If you want to see the actual, you need to run the following:

zsh> pyenv which python3
/usr/local/bin/python3

zsh> /usr/local/bin/python3 -V
Python 3.6.8

To shift between different versions, you can simply run:

zsh> pyenv global 3.11.4

zsh> python -V
Python 3.11.4

zsh> which python
python: aliased to python3

zsh> pyenv which python
/Users/johndoe/.pyenv/versions/3.11.4/bin/python

zqh> pyenv versions
system
3.6.8
3.6.9
3.6.15
3.8.16
3.8.17
3.9.9
3.10.6
* 3.11.4 (set by /Users/johndoe/.pyenv/version)

shell vs. local vs. global vs. system

Use-cases

Let’s explore the different commands and their use-cases.

To ensure that this python version is gonna be used by default:

zsh> pyenv global 3.11.4

To set an application-specific python version:

zsh> pyenv local 3.11.4

The above command creates a .python-version file in the current directory. If pyenv is active in this an environment, the file will automatically activate this version.

To set a shell-specific python version:

zsh> pyenv shell 3.11.4

The above command activates the version specific by setting the `PYENV_VERSION“ environment variable. It overwrites any application or global setting you have made. To deactivate the version, you need to use the –unset flag:

zsh> echo $PYENV_VERSION
3.11.4
zsh> pyenv shell --unset

Resolution

The System Python is overwritten by pyenv global (~/.pyenv/version).

The pyenv global is overwritten by pyenv local (.python-version file).

The pyenv local is overwritten by pyenv shell ($PYENV_VERSION).

Thus, to determine which version of python to use, pyenv will first look for $PYENV_VERSION, then .python-version then ~/.pyenv/version before finally settling down on the Python System if none of the above have been resolved.

Example

zsh> mkdir /tmp/test && cd /tmp/test

zsh> pyenv versions
* system (set by /Users/johndoe/.pyenv/version)
3.6.8
3.6.9
3.6.15
3.8.16
3.8.17
3.9.9
3.10.6
3.11.4
zsh> python -V
Python 3.6.8

zsh> pyenv local 3.8.16
zsh> ls -a
.       ..      .python-version
zsh> .python-version
Python 3.8.16
zsh> python -V
Python 3.8.16

zsh> python shell 3.9.9
zsh> echo $PYENV_VERSION
3.9.9
zsh> python -V
Python 3.9.9

And the other way around you can coax it out, layer by layer:

zsh> pyenv shell --unset
zsh> echo $PYENV_VERSION

zsh> python -V
Python 3.8.16

zsh> rm .python-version
zsh> python -V
Python 3.6.8

zsh> pyenv versions
* system (set by /Users/johndoe/.pyenv/version)
3.6.8
3.6.9
3.6.15
3.8.16
3.8.17
3.9.9
3.10.6
3.11.4

Virtual environments and pyenv

To quote this realpython.com article, virtual environments and pyenv are a match made in heaven. Whether you use virtualenv or venv, pyenv plays nicely with either.

You can create virtual environment using the following template:

pyenv virtualenv <python_version> <environment_name>

You can activate your environment running the following:

pyenv local <environment_name>

You can also do it manually:

zsh> pyenv activate <environment_name>
zsh> pyenv deactivate