If you’re like me (or Randall Munroe), the results of typingwhich -a python on your macOS machine is… devastating:
After many years of frustration, here are my recommendations forinstalling Python and Python-written utilities on macOS.
I use Python for library development, web development withDjango, and scripting. I use Python primarily from the commandline. I don’t really use Python for data science—at least not withJupyter notebooks. I have absolutely no idea how to use conda. If youuse Python primarily for data science, this guide is not writtenfor you—there may be better solutions that I simply do not use.If your use case sounds similar to mine, please read on!
It seems pretty obvious that you should install Python using theinstaller from Python’s official website.
Do you want to die?
The problem with this is that Python installs itself in a place that isdifficult to manage without using administrator (i.e., sudo) privileges.This is okay for beginners, or people who only touch Python every sooften. However, for my use cases, where I’m testing my code inmultiple versions of Python, and have multiple virtual environments,this becomes bad news.
If you haven’t used Homebrew to install things on your Mac, go get itright now! I’ll wait.
We’re going to use Homebrew to install pyenv.
Do not install Python with Homebrew, though. Instead…
pyenv
Install pyenv so that you can install multiple versions of Pythonand switch between them with ease:
brew install pyenv
Now you need to make sure your system can see the shims that pyenvcreates. Shims are fake versions of all programs associated witha Python install. This allows pyenv to intercept a command andredirect it to the current or desired version of Python.
To do this, prepend the shim directory to your path. Add this line to thebottom of your shell startup file (either ~/.zshrc or~/.bash_profile depending on either how new your Mac is or if you’vecustomized your shell ¯_(ツ)_/¯):
export PATH="$HOME/.pyenv/shims:$PATH"
The shim directory must be the first thing in your path, or atleast, 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 closingyour current terminal tab/window, and opening a newone, 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 thefollowing 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 fromembarrassment.
Now let’s install a modern version of Python. As of this writing, thecurrent stable version of CPython is Python 3.8.1. Let’s install it withPyenv!
pyenv install 3.8.1
If you want to see all the versions of Python that pyenv can installfor you, you can list them:
pyenv install --list
This should be a pretty long list, with all kinds of alternate Pythoninterpreters, including PyPy and Stackless. Install as many asyou need!
Now tell pyenv the default version you want to use. Do this with pyenvglobal:
pyenv global 3.8.1
Now try it! Check what happens when you ask for the current Pythonversion:
$ python --version
Python 3.8.1
Since I’m paranoid that something on my system will break because it’sexpecting python to be Python 2.7 (yikes!) that’s pre-installed byApple on my system, I do the following:
pyenv global system 3.8.1
This instructs to pyenv that it should check if the executable isinstalled on the system path first, and then try the selected Pythonversion—in my case, CPython 3.8.1. You might be able to get away withpyenv 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 ofpyenv is too old. Upgrade pyenv using Homebrew:
brew upgrade pyenv
I often want to install utilities that are written in Python, andavailable to install via pip. For example, I like installingblack 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 dosome arcane shell-fu to install multiple packages in one command line.Use xargs -n1 to call an command with exactly one of its inputarguments:
# Install isort, mypy, snakeviz, pygments, and tqdm all on one line!
echo isort mypy snakeviz pygments tqdm | xargs -n1 pipx install
For each of my projects, I want an isolated environment to installpackages. Python has historically done this… poorly. The hackysolution for years has been virtual environments, but they requirea 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 noticeda notable improvement in my workflow. However, some of the decisionsthat pipenv’s original creator made were questionable to say theleast. pipenv is under new management, but some of the decisions andattitudes of the original creator have left scars on the currentstate of the project.
So then I switched to Poetry.
Poetry is my current choice for project dependency management. You caninstall it with Homebrew:
Basic usage can be found on Poetry’s website, but here’sa 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 yourlibrary, or deploys your app somewhere.
poetry add --dev black isort pylint To actually run my application, I tend to use poetry shell toactivate 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 thedocs to learn more.
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! 🍎🐍