Today’s article is about one of the most impressive pieces of software Python-related I have ever seen:
pyenv is the most clever way of installing Python on your system and allows you to install and manage several different versions of
Python specifying a different Python version for every project you have.
The installation process
If you’re on a Mac and you are a homebrew user there’s a specific formulae for that. If you don’t know homebrew, it’s a package manager for macOS and it’s another great piece of software, go and check it out, don’t miss it! If you have homebrew installed, you can simply get pyenv by using the following command:
1$ brew install pyenv
If you are on Linux, you can simply download it from GitHub but the most convenient way is to use the pyenv-installer that is a simple script that will install it automatically on your distro, whatever it is, in the easiest possible way.
If you are on Windows… come on man, the 90s are over! ;) Ok ok, no hard feelings guys, I was just joking! The fact is that this software doesn’t exist on Windows but don’t lose faith, there are at least two possibilities. The first one is to use pyenv-win, which is a specific fork working on windows by Kiran Kumar Kotari. I have to admit that I have never tried it though, so try it by yourself and leave a comment here to let us know how it was. The second option (the one I actually use when I’m on Windows) is… to use the Linux version on WSL2. I know, I know… you may think that this is not a real solution, it’s like cheating… but I honestly think that in 2022 if you are a developer you should consider moving to Unix or Unix like systems, like macOS or Linux, so even when I’m on Windows I heavily rely on the Windows Subsystem for Linux, which I consider the best thing Microsoft did in the last 40 years…
However, to write this article since I have already installed and configured
pyenv on all my systems, I have created a small Linux Lithium
vm so to be able to start from scratch along with you. If you don’t know Lithium, check their website, it’s
a very lightweight distro based on Debian and ideally a continuation of the #!Linux,
which was an incredible distro based on Debian discontinued in 2013…
However, let’s start the installation!
Start your Linux terminal and execute the pyenv-installer script by executing
curl https://pyenv.run | bash.
1dave@hell:~$ curl https://pyenv.run | bash 2 % Total % Received % Xferd Average Speed Time Time Time Current 3 Dload Upload Total Spent Left Speed 4100 270 100 270 0 0 286 0 --:--:-- --:--:-- --:--:-- 286 5Cloning into '/home/dave/.pyenv'... 6remote: Enumerating objects: 979, done. 7remote: Counting objects: 100% (979/979), done. 8remote: Compressing objects: 100% (451/451), done. 9remote: Total 979 (delta 564), reused 659 (delta 401), pack-reused 0 10Receiving objects: 100% (979/979), 481.70 KiB | 9.83 MiB/s, done. 11Resolving deltas: 100% (564/564), done. 12Cloning into '/home/dave/.pyenv/plugins/pyenv-doctor'... 13remote: Enumerating objects: 11, done. 14remote: Counting objects: 100% (11/11), done. 15remote: Compressing objects: 100% (9/9), done. 16remote: Total 11 (delta 1), reused 5 (delta 0), pack-reused 0 17Unpacking objects: 100% (11/11), done. 18Cloning into '/home/dave/.pyenv/plugins/pyenv-installer'... 19remote: Enumerating objects: 16, done. 20remote: Counting objects: 100% (16/16), done. 21remote: Compressing objects: 100% (13/13), done. 22remote: Total 16 (delta 1), reused 7 (delta 0), pack-reused 0 23Unpacking objects: 100% (16/16), done. 24Cloning into '/home/dave/.pyenv/plugins/pyenv-update'... 25remote: Enumerating objects: 10, done. 26remote: Counting objects: 100% (10/10), done. 27remote: Compressing objects: 100% (6/6), done. 28remote: Total 10 (delta 1), reused 5 (delta 0), pack-reused 0 29Unpacking objects: 100% (10/10), done. 30Cloning into '/home/dave/.pyenv/plugins/pyenv-virtualenv'... 31remote: Enumerating objects: 61, done. 32remote: Counting objects: 100% (61/61), done. 33remote: Compressing objects: 100% (54/54), done. 34remote: Total 61 (delta 11), reused 28 (delta 1), pack-reused 0 35Unpacking objects: 100% (61/61), done. 36Cloning into '/home/dave/.pyenv/plugins/pyenv-which-ext'... 37remote: Enumerating objects: 10, done. 38remote: Counting objects: 100% (10/10), done. 39remote: Compressing objects: 100% (6/6), done. 40remote: Total 10 (delta 1), reused 6 (delta 0), pack-reused 0 41Unpacking objects: 100% (10/10), done. 42 43WARNING: seems you still have not added 'pyenv' to the load path. 44 45# Load pyenv automatically by appending 46# the following to 47~/.bash_profile if it exists, otherwise ~/.profile (for login shells) 48and ~/.bashrc (for interactive shells) : 49 50export PYENV_ROOT="$HOME/.pyenv" 51command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH" 52eval "$(pyenv init -)" 53 54# Restart your shell for the changes to take effect. 55 56# Load pyenv-virtualenv automatically by adding 57# the following to ~/.bashrc: 58 59eval "$(pyenv virtualenv-init -)"
Ok, that was easy, wasn’t it? Now all you have to do is to add the pyenv root directory at the beginning of your
Now edit your ~/.bashrc and add the following lines:
1export PYENV_ROOT="$HOME/.pyenv" 2command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH" 3eval "$(pyenv init -)"
That’s it, now log out and log in again to make your system accept and load these changes.
If everything worked as expected, you can try to execute
pyenv and if you get something, you did it, the installation was ok, read on.
Now that we have
pyenv installed, we can start using it. On my Lithium system, the default python version that comes installed with the
system is the 2.7, which is terrible, isn’t it? So we will download and install the last Python version available that at the moment is the 3.10.4.
With just a command,
pyenv will download the source code and build our Python version directly on our system for us!
Before that, however, some libraries has to be installed and that are needed to build Python.
So, since I’m on a Lithium Linux (that is a flavor of a Debian distro), I have to use the following command to install all the reccomended dependencies:
1dave@hell:~$ sudo apt-get install -y build-essential libssl-dev zlib1g-dev libbz2-dev \ 2libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \ 3xz-utils tk-dev libffi-dev liblzma-dev python-openssl git
If you are on a different distro, you can find the correct command to install all these dependencies here.
Ok, now that our dependencies are installed we are ready to install Python 3.10.4 by using
pyenv install 3.10.4 -v:
1dave@hell:~$ pyenv install 3.10.4 -v 2... 3Looking in links: /tmp/tmpzxxvet16 4Processing /tmp/tmpzxxvet16/setuptools-58.1.0-py3-none-any.whl 5Processing /tmp/tmpzxxvet16/pip-22.0.4-py3-none-any.whl 6Installing collected packages: setuptools, pip 7 WARNING: The scripts pip3 and pip3.10 are installed in '/home/dave/.pyenv/versions/3.10.4/bin' which is not on PATH. 8 Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location. 9Successfully installed pip-22.0.4 setuptools-58.1.0 10/tmp/python-build.20220506212137.7185 ~ 11~ 12Installed Python-3.10.4 to /home/dave/.pyenv/versions/3.10.4
-v is just to have a verbose output because the installation won’t be super fast and if you don’t put this option, you could think
that the system is hanging. On my Lithium vm, installed on a 2018 MacBook Pro, the installation took about 2 minutes.
Ok, now that we have installed Python 3.10.4, let’s ask to pyenv which version we have available now:
1dave@hell:~$ pyenv versions 2* system (set by /home/dave/.pyenv/version) 3 3.10.4
As you can see, we have the system version (that is the one bundled with my distro) and the 3.10.4 that I’ve just installed.
Since the currently selected version is the system version (that’s what that asterisk near system stands for), if we run the
command we are welcomed by the system version:
1dave@hell:~$ python 2Python 2.7.16 (default, Oct 10 2019, 22:02:15) 3[GCC 8.3.0] on linux2 4Type "help", "copyright", "credits" or "license" for more information. 5>>>
So, if we want to make sure that for the current user, the default Python installation will be the 3.10.4 one, we can just use the
keyword like this:
1dave@hell:~$ pyenv global 3.10.4 2dave@hell:~$
Now, if we run the command
python again we get the 3.10.4 version:
1dave@hell:~$ python 2Python 3.10.4 (main, May 6 2022, 21:22:43) [GCC 8.3.0] on linux 3Type "help", "copyright", "credits" or "license" for more information. 4>>>
That’s cool uh?
Ok, but what if I have a specific project where I would like to use the 3.6.2 version for example?
Let’s start by installing this version on our system:
1dave@hell:~$ pyenv install 3.6.2 2Downloading Python-3.6.2.tar.xz... 3-> https://www.python.org/ftp/python/3.6.2/Python-3.6.2.tar.xz 4Installing Python-3.6.2... 5Installed Python-3.6.2 to /home/dave/.pyenv/versions/3.6.2 6 7dave@hell:~$
Now, let’s see the version available on our system:
1dave@hell:~$ pyenv versions 2 system 3* 3.10.4 (set by /home/dave/.pyenv/version) 4 3.6.2 5dave@hell:~$
Cool, now let’s enter in our project directory and let’s set a specific Python version for that specific project by the
1dave@hell:~/mysupersecretproject$ pyenv local 3.6.2 2dave@hell:~/mysupersecretproject$
Let’s remain in our project directory and let’s see the Python versions we can use now:
1dave@hell:~/mysupersecretproject$ pyenv versions 2 system 3 3.10.4 4* 3.6.2 (set by /home/dave/mysupersecretproject/.python-version) 5dave@hell:~/mysupersecretproject$
Can you see it? Now the default version is the 3.6.2 and
pyenv tells us even why this would be the default version for this directory,
in fact with the
local keyword we have created a hidden file named
.python-version that specifies the python version that has to be
used for our project! Let’s do some tests:
1dave@hell:~/mysupersecretproject$ python 2Python 3.6.2 (default, May 6 2022, 21:49:46) 3[GCC 8.3.0] on linux 4Type "help", "copyright", "credits" or "license" for more information. 5>>> 6dave@hell:~/mysupersecretproject$ cat .python-version 73.6.2 8dave@hell:~/mysupersecretproject$ cd .. 9dave@hell:~$ pyenv versions 10 system 11* 3.10.4 (set by /home/dave/.pyenv/version) 12 3.6.2 13dave@hell:~$
Great, the versioning is working great and the 3.6.2 is used only in the directory we chose for it!
Ok, I know, some of you are thinking “I don’t want to use the standard Python installation, I need Jython, or Anaconda or Miniconda or …”.
Well, the good news is that
pyenv lets you choose other Python distributions as well. To know all the versions available try to use
pyenv install --list and hold on tight!
1dave@hell:~$ pyenv install --list 2Available versions: 3 2.1.3 4 2.2.3 5 2.3.7 6 2.4.0 7 2.4.1 8... 9 3.3.4 10 3.3.5 11 3.3.6 12 3.3.7 13 3.4.0 14... 15 3.10.2 16 3.10.3 17 3.10.4 18 3.11.0a7 19 3.11-dev 20 activepython-2.7.14 21 activepython-3.5.4 22 activepython-3.6.0 23 anaconda-1.4.0 24 anaconda-1.5.0 25 anaconda-1.5.1 26... 27 anaconda3-2021.11 28 graalpython-20.1.0 29 graalpython-20.2.0 30 graalpython-20.3.0 31 graalpython-21.0.0 32 graalpython-21.1.0 33... 34 ironpython-188.8.131.52 35 ironpython-2.7.7 36 jython-dev 37 jython-2.5.0 38 jython-2.5-dev 39 jython-2.5.1 40 jython-2.5.2 41... 42 mambaforge 43 mambaforge-4.10.1-4 44 mambaforge-4.10.1-5 45 mambaforge-4.10.3-10 46 micropython-dev 47 micropython-1.9.3 48 micropython-1.9.4 49 micropython-1.10 50 micropython-1.11 51... 52 miniconda3-4.5.11 53 miniconda3-4.5.12 54 miniconda3-4.6.14 55 miniconda3-4.7.10 56 miniconda3-4.7.12 57... 58 stackless-3.4-dev 59 stackless-3.4.2 60 stackless-3.4.7 61 stackless-3.5.4 62 stackless-3.7.5 63dave@hell:~$ 64
There are currently more than 560 versions of Python you can choose from.
Behind the magic
Now that you know the basic usage of pyenv you might wondering how it works. Well, it turns out that as usual, it is easier than you may expect.
Pyenv works by adding a special directory called
shims in front of your PATH environment variable, like this:
and in this directory, there are all the python commands you are used to, like
So the command you have inserted is actually hijacked by
pyenv, which can pass the command to the Python installation you like.
When a shim is executed, the Python version to be used is chosen in the following order:
- at first, it checks for a Python version specified in the
- then it looks if there is a
.python-versionfile in the current directory, created by the
- then it checks in order all the parent directories till the root one, looking for
.python-versionfiles to be used
- in the end, it looks for a
$(pyenv root)/versionfile, that is the one that is created when you use the
All the Python versions installed by
pyenv can be found under the directory
$(pyenv root)/versions and can be uninstalled by the
pyenv uninstall command.
To sum up
In this article, we have seen how easy it is to manage different Python versions either on a per-user or a per-project basis with pyenv. For more information visit the official GitHub page of the project and feel free to submit pull requests or file bugs on the issue tracker, the project is active and actively supported by hundreds of contributors.
Happy coding! D.