Working with MacPorts and Python
Date: 2022-02-12For a number of reasons, I prefer MacPorts over Homebrew to manage packages on my macOS machines.
Working with multiple versions of Python can be a headache. In this post, I’ll describe how I use MacPorts to manage Python versions for development purposes.
For context, I’ve tried a number of different ways to manage Python on my macOS computers over the years: Anaconda, Homebrew-provided Python packages, pyenv, etc., and this is what I found works best for me.
Unlike Homebrew, MacPorts does not clutter up your /usr/local
directory, by
default installing packages to /opt/local
instead. And unlike Anaconda,
MacPorts Python packages work harmoniously with other packages on your system,
and does not try to supplant your system
compiler.
With that said, let’s move on to a few practical tips on using MacPorts to manage Python versions. In the following instructions, I assume that you have already installed MacPorts on your system1.
Managing multiple versions of Python and pip
To install a specific version of of Python along with the corresponding version of
pip
, do
sudo port install py<major><minor>-pip
where <major>
and <minor>
are the major and minor version number of Python
that you are trying to install2. For example, if you wanted to install Python
3.10 (the latest version at the time of writing this (2022-02-12)), you would
run the following command.
sudo port install py310-pip
To activate a particular version of Python and pip, you would use the port select
command. You can put the following snippet in your ~/.bash_profile
3 to
simplify the process.
# Usage: activate_py <major_version><minor_version>
# Example:
#
# activate_py 310
#
# will activate Python 3.10
activate_py() {
local major_version=$(echo $1 | cut -c 1)
local minor_version=$(echo $1 | cut -d "$major_version" -f2)
sudo port select --set python python"$1"
sudo port select --set python"$major_version" python"$1"
sudo port select --set pip pip"$1"
sudo port select --set pip"$major_version" pip"$1"
}
The above snippet defines a function named activate_py
that can be invoked to
switch between Python versions. For example:
activate_py 310
will activate Python 3.10 and the corresponding version of pip
.
Managing packages
Python packages can either be installed within a virtual environment or system-wide. I’ll describe both methods and when to use them below.
Installing packages in virtual environments
In general, it is best to have a distinct Python virtual environment for each
Python project you are working on that may have conflicting dependencies. I
have the following snippet in my ~/.bash_profile
to create a virtual
environment in a directory called ~/.venvs
(the directory will be created if
it does not yet exist).
# Create a new Python virtual environment
#
# Usage:
#
# make_venv <project_name>
#
make_venv() {
mkdir -p ~/.venvs
# We redirect stderr to stdout for the command 'python --version' since
# for Python 2.x, the version is output to stderr instead of stdout.
local major_version=$(python --version 2>&1 | cut -c 8)
if [[ $major_version == "3" ]]; then
python -m venv ~/.venvs/$1
elif [[ $major_version == "2" ]]; then
python -m virtualenv ~/.venvs/$1
else
echo "Error: Python major version was not determined to be either 2 or 3."
return 1
fi
}
For example, if you invoke the following command:
make_venv myproject
a virtual environment named myproject
will be created at
~/.venvs/myproject
.
Note that if you are using Python 24, you will also need to install
the appropriate version of the virtualenv
package separately in order for the
above snippet to function. For example, if you are using Python 2.7, you will
also need to run the following command to enable the creation of Python 2.7
virtual environments:
sudo port install py27-virtualenv
I also have the following companion snippet in my ~/.bash_profile
to quickly
activate virtual environments that have been created with make_venv
.
# Activate a Python virtual environment
#
# Usage:
#
# activate <virtual environment name>
#
activate() {
source ~/.venvs/$1/bin/activate
}
For example, invoking activate myproject
at the command line will activate
the myproject
virtual environment, automatically pointing your python
and
pip
invocations to the correct binaries, and bringing the packages installed
within the virtual environment into scope for usage in Python scripts. Note
that you must activate the virtual environment before installing packages in
order to correctly isolate the package installations.
Installing packages system-wide (advanced)
If you do not anticipate working on multiple Python projects with potentially conflicting dependencies, this route might be the one for you, since you would not need to activate virtual environments every time you wanted to do anything Python-related that goes beyond Python’s standard library. However, if you are still new to juggling multiple versions of Python, I would recommend simply sticking with the virtual environment approach outlined above. It is easier to go from virtual environments to system-wide package installations than the other way around5.
Popular packages such as numpy
and matplotlib
have dedicated ports and can
be installed system-wide by invoking:
sudo port install py-<packagename>
For example, running the following command
sudo port install py-numpy
will install numpy
system-wide for the version of Python that you currently
have active. If you want to check whether a port exists for a given Python
package, you can use the port search
command:
port search py-<packagename>`
If you want to install a package system-wide that does not have an existing
port, you can simply use pip
:
sudo pip install <packagename>
Ideally, you would also have removed other Python distributions that you installed yourself (e.g. the one from Python.org, Anaconda, etc.), unless you are a bit more advanced and thus adept at managing your
PATH
environment variable appropriately to work with multiple Python distributions.↩︎The
sudo
in the invocation is required if you install MacPorts to the default location (/opt/local
). However, if the MacPorts root directory is somewhere else, e.g., in your home directory (e.g.,~/macports
), then you may not need to prefix your invocation withsudo
.↩︎If you are using
zsh
, put the snippet in~/.zprofile
instead.↩︎Python 2 support ended on January 1, 2020, so you really should not be using it unless you are working with legacy code that is completely unfeasible to port to Python 3.↩︎
The two methods can also co-exist if you pass the command line flag
--system-site-packages
to thepython -m venv
invocation, but that is beyond the scope of this post, since it is unlikely that you will need to do this.↩︎