Multiple Python versions on Mac and how to set it up correctly

Introduction

It is possible to have multiple versions of Python installed on a single MacBook. This can be done by using a version manager such as conda or using virtual environments with appropriate Python versions for each of your specific tasks. Sometimes it makes the usage of the specific version of Python, and alignment between installed packages and the pip version very confusing. You probably experienced the situation when you installed the Python package via pip and saw such an error during the validation of the successful installation:

python                                                                                                                             
Python 3.10.6 (main, Aug 30 2022, 05:12:36) [Clang 13.1.6 (clang-1316.0.21.2.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import <package_name>
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named '<package_name>'

Let's dive deeper together into possible issues you may have and structure all the knowledge. There is nothing confusing at all!

Which

First of all, to understand the number of possible Python versions installed on your Mac, you can simply check it in your bash console by running:

python <press Tab>

python python3-config python3.8 python3.9-config python.app python3.10 python3.8-config pythonw python3 python3.10-config python3.9

To see precisely the path to the Python interpreter that is used during the call (for my specific case): python, python3.8, etc.

which python
/usr/local/bin/python3.10

which python3.10            
/Users/daniilkorbut/anaconda3/bin/python3.8

Just a reminder, you can always type the full path to the Python interpreter and it will be the same as the shortcut name:

python3.10                                                                                                                                
Python 3.10.6 (main, Aug 30 2022, 05:12:36) [Clang 13.1.6 (clang-1316.0.21.2.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>

/usr/local/bin/python3.10                                                                                                                 
Python 3.10.6 (main, Aug 30 2022, 05:12:36) [Clang 13.1.6 (clang-1316.0.21.2.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>

$PATH environment variable

And now we came to an important part, how to understand why python3.10 was found in exact /usr/local/bin/python3.10 directory. It means that the /usr/local/bin directory is included in the $PATH environment variable. We can view the content of this variable by running:

echo $PATH                                         
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

: is a separator of the path in such syntax and the order of these paths defines the order in which the system tries to find the python3 interpreter (for example) when you run python3 without specification of /usr/local/bin/python3 . You can always add the additional path to the $PATH variable to the beginning or the end, depending on your requirements. Depends on whether you use bash or zsh, you should have to edit .bash_profile or .zshrc for such purpose.

For zsh you should run nano .zshrc and add the necessary path to the new Python interpreter to the file. This example illustrates the addition of anaconda3 for both append and prepend options. For the append version, the new path for anaconda3 will have the lowest priority among existing ones, and for the prepend version - the highest priority.

# append
path+=('/Users/daniilkorbut/anaconda3/bin')
# or prepend
path=('/Users/daniilkorbut/anaconda3/bin' $path)
# export to sub-processes (make it inherited by child processes)
export PATH

For bash the same logic nano .bash_profile :

# append
PATH='${PATH}:/home/david/pear/bin'
# or prepend
PATH='/home/david/pear/bin:${PATH}'
export PATH

Don't forget to restart the bash console after these changes! Now, when you run echo $PATH the changes from the previous step should be reflected.

Alias

One of the ways to redefine the Python interpreter path but only for the active terminal session (the changes will go away after restart) is the alias command. You can specify alias python=python3.10 or alias python=/usr/local/bin/python3.10 to use the python word instead of python3.10 for your interpreter calls. To keep these changes for the next terminal session you can add the lines above to the already known .zshrc or .bash_profile files. If you don't want to use further aliased command, unalias python will revert the changes for the current session. In case of using aliases the output will be a bit different for which command:

alias py=python3.9

which py
py: aliased to python3.9

which python3.9
/usr/local/bin/python3.9

Another but very rare example of confusion that may appear, is the alignment between pip and python versions on the same machine. You can resolve it quickly by verifying specific python and pip locations on your machine:

which pip3.9                                      
/usr/local/bin/pip3.9

which python3.9                       
/usr/local/bin/python3.9

for this example the prefix of the path is the same which means pip3.9 install <package name> command will install the Python package for python3.9 interpreter located in /usr/local/bin/python3.9 .

Virtual environment

You can also use virtual environments to manage different versions of Python and their dependencies. Virtualenv is a tool that allows you to create isolated Python environments for different projects, which can help you avoid conflicts between packages that have different version requirements.

To install virtualenv, you can use pip:

pip install virtualenv

Once virtualenv is installed, you can create a new virtual environment with specific Python version by running:

virtualenv -p /usr/bin/python3 envname

You can deactivate the environment using the command deactivate .