Installing Python on macOS (without going insane)
If you’re like me (or Randall Munroe), the results of typing
which -a python
on your macOS machine is… devastating:
After many years of frustration, here are my recommendations for installing Python and Python-written utilities on macOS.
So what do I use Python for?
I use Python for library development, web development with
Django, and scripting. I use Python primarily from the command
line. I don’t really use Python for data science—at least not with
Jupyter notebooks. I have absolutely no idea how to use conda
. If you
use Python primarily for data science, this guide is not written
for you—there may be better solutions that I simply do not use.
If your use case sounds similar to mine, please read on!
Do NOT install Python from python.org
It seems pretty obvious that you should install Python using the installer from Python’s official website.
The problem with this is that Python installs itself in a place that is
difficult to manage without using administrator (i.e., sudo
) privileges.
This is okay for beginners, or people who only touch Python every so
often. However, for my use cases, where I’m testing my code in
multiple versions of Python, and have multiple virtual environments,
this becomes bad news.
Homebrew
If you haven’t used Homebrew to install things on your Mac, go get it right now! I’ll wait.
We’re going to use Homebrew to install pyenv
.
Do not install Python with Homebrew, though. Instead…
Manage multiple Python versions with pyenv
Install pyenv so that you can install multiple versions of Python and switch between them with ease:
brew install pyenv
Putting pyenv
in charge by adding it to the PATH
Now you need to make sure your system can see the shims that pyenv
creates. Shims are fake versions of all programs associated with
a Python install. This allows pyenv
to intercept a command and
redirect it to the current or desired version of Python.
To do this, prepend the shim directory to your path. Add this line to the
bottom of your shell startup file (either ~/.zshrc
or
~/.bash_profile
depending on either how new your Mac is or if you’ve
customized your shell ¯\_(ツ)_/¯):
export PATH="$HOME/.pyenv/shims:$PATH"
The shim directory must be the first thing in your path, or at
least, it has to come before /usr/bin/
and /usr/local/bin
.
Now that you have added the line to your shell startup file, restart your shell for it to take effect. You can do this by closing your current terminal tab/window, and opening a new one, or in the same terminal/tab, you can do the following:
exec $SHELL
Let’s make sure our PATH
looks right. To look at your path, use the
following line:
printenv PATH
This is what my PATH
environment variable looks like on my laptop:
/Users/eddieantonio/.pyenv/shims:/usr/local/bin:/usr/local/sbin:/bin:/usr/sbin:/sbin:/Library/TeX/texbin:/opt/X11/bin:/Users/eddieantonio/.local/bin
Kind of. I removed a few entries from my PATH to save myself from embarrassment.
Installing a Python version!
Now let’s install a modern version of Python. As of this writing, the current stable version of CPython is Python 3.8.1. Let’s install it with Pyenv!
pyenv install 3.8.1
If you want to see all the versions of Python that pyenv
can install
for you, you can list them:
pyenv install --list
This should be a pretty long list, with all kinds of alternate Python interpreters, including PyPy and Stackless. Install as many as you need!
Choose your default Python
Now tell pyenv
the default version you want to use. Do this with pyenv
global
:
pyenv global 3.8.1
Now try it! Check what happens when you ask for the current Python version:
$ python --version
Python 3.8.1
Since I’m paranoid that something on my system will break because it’s
expecting python
to be Python 2.7 (yikes!) that’s pre-installed by
Apple on my system, I do the following:
pyenv global system 3.8.1
This instructs to pyenv
that it should check if the executable is
installed on the system path first, and then try the selected Python
version—in my case, CPython 3.8.1. You might be able to get away with
pyenv global 3.8.1
and not use the pre-installed system Python at all.
If you don’t see the version you want to install, perhaps your installed version of
pyenv
is too old. Upgrade pyenv
using Homebrew:
brew upgrade pyenv
pipx
I often want to install utilities that are written in Python, and
available to install via pip
. For example, I like installing
black to format my code for me.
Do not do the following 🙅:
sudo pip install black
Instead, I’d like these globally-installed executables to be installed, without installing themselves on my Python path.
This is exactly the problem that pipx tries to solve.
Install it using Homebrew:
brew install pipx
Then install your favourite Python executables 👍!
pipx install black
Sadly, pipx
will only install one package at a time, but we can do
some arcane shell-fu to install multiple packages in one command line.
Use xargs -n1
to call an command with exactly one of its input
arguments:
# Install isort, mypy, snakeviz, pygments, and tqdm all on one line!
echo isort mypy snakeviz pygments tqdm | xargs -n1 pipx install
Managing project environments with poetry
For each of my projects, I want an isolated environment to install
packages. Python has historically done this… poorly. The hacky
solution for years has been virtual environments, but they require
a lot of manual work, and you need to remember to create a virtual environment,
activate it, place all the packages you installed in requirements.txt
,
and so on.
What a mess!
Then came pipenv. I’ve used pipenv for about a year, and noticed
a notable improvement in my workflow. However, some of the decisions
that pipenv
’s original creator made were questionable to say the
least. pipenv
is under new management, but some of the decisions and
attitudes of the original creator have left scars on the current
state of the project.
So then I switched to Poetry.
Poetry is my current choice for project dependency management. You can install it with Homebrew:
brew install poetry
Basic usage can be found on Poetry’s website, but here’s a quick tutorial:
Make a brand new project using poetry new
:
poetry new my-awesome-app
cd
into the project, and install some dependencies:
cd my-awesome-app
poetry add django
You can also specify dependencies you only use in development—that is, these packages should not be installed when somebody downloads your library, or deploys your app somewhere.
poetry add --dev black isort pylint
To actually run my application, I tend to use poetry shell
to
activate the current environment.
poetry shell
For example, in my Django app:
# this will not work -- not in virtualenv:
$ django-admin version
zsh: command not found: django-admin
$ poetry shell
Spawning shell within /Users/eddieantonio/Library/Caches/pypoetry/virtualenvs/my-awesome-app-VMVa3PrX-py3.7
# this works now 😄
(my-awesome-app-VMVa3PrX-py3.7) $ django-admin version
3.0.2
This is just scratching the surface of using Poetry. Read the docs to learn more.
Putting it all together: installing Python on macOS
Here is a dangerously copy-pastable snippet to install all the things:
# Install Homebrew:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
# Install pyenv, pipx, and poetry with Homebrew:
brew install pyenv pipx poetry
# Make sure pyenv is on the path:
echo 'export PATH="$HOME/.pyenv/shims:$PATH"' > ~/.zshrc
# Restart your shell (so we have the updated path):
exec $SHELL
# Install a modern version of Python using pyenv:
# (Python 3.8.1 is the latest stable version as of this writing)
pyenv install 3.8.1
# Tell pyenv to try using the system Python 2.x before using our pyenv
# installed Python 3.8.1
pyenv global system 3.8.1
# Install Python-based utilities globally using pipx:
# May I suggest black, isort, and mypy?
echo black isort mypy | xargs -n1 pipx install
Happy coding! 🍎🐍