Wilson Mar bio photo

Wilson Mar


Calendar YouTube Github


The tools to dynamically install and use different versions of Python, packages, all within Virtualenv

US (English)   Norsk (Norwegian)   Español (Spanish)   Français (French)   Deutsch (German)   Italiano   Português   Estonian   اَلْعَرَبِيَّةُ (Egypt Arabic)   Napali   中文 (简体) Chinese (Simplified)   日本語 Japanese   한국어 Korean


This tutorial describes the different options to install, uninstall, configure, and use various versions of Python with its various packages, all running in a virtual environment also managed by pyenv.

NOTE: Content here are my personal opinions, and not intended to represent any employer (past or present). “PROTIP:” here highlight information I haven’t seen elsewhere on the internet because it is hard-won, little-know but significant facts based on my personal research and experience.

Why pipenv?

pipenv brings “the best of all packaging worlds (bundler, composer, npm, cargo, yarn, etc.) to the Python world.”

The value of pyenv is:

  • Install Python in your user space (without need for sudo)
  • Install multiple parallel versions of Python
  • Dynamically specify the exact Python version you want
  • Switch between installed versions without resetting your bash session

What is the magic of pyenv?

In this article I take a carefully crafted narrated tour. Here is a hands-on “deep dive” tutorial so you better grasp the complexities in a shorter time. Why? Because I haven’t seen one on the internet.

pyenv uses a technique called “rehashing” so it can switch among multiple versions of Python2 or Python3.

Commands for the operating system to execute “python3” are intercepted by a shim executable which passes commands along to the actual Python installation of the desired version.

That’s achieved by a line at the bottom of your ~/.bash_profile which makes the operating system search in the shims folder for programs:

export PATH="$PYENV_ROOT/shims:$PATH"

This technique is possible because the operating system searches for executables in folders in the PATH from left to right.

Thus, which aws would return:



The alternative to Pyenv is Conda and MiniConda.

What are installed?

PROTIP: Before installing things, first see what is already installed.

  1. Get a list of the various locations where Python is installed (by various installers):

    type -a python

    A new macOS version would show:

    python is /usr/bin/python

    PROTIP: The /usr/bin/ folder is owned by the operating system, so elevated sudo priviledges are required to modify files in it (such as “python”). So Homebrew and other installers install to the User-owned /usr/local/ which does NOT require sudo to access.

    Different installers install Python in different paths (but instead of “john_doe”, you’ll see your own user name):

    python is /Users/john_doe/.pyenv/shims/python
    python is /Users/john_doe/anaconda3/bin/python
    python3 is /usr/local/opt/python@3.11/bin/python3
    python is /usr/local/anaconda3/bin/python
    python is /usr/bin/python

    You are good to go with Pipenv if you see in the first response to the type -a python command (but instead of “john_doe”, you’ll see your own user name):

    python3 is /Users/john_doe/.pyenv/shims/python3

    PROTIP: There may be several Python executables installed in different folders. But the Python program actually executed is the first one that the operating system finds among folders defined in your PATH system variable defined in ~/.bash_profile or ~/.zshenv. The operating system searches for executables in each folder in the PATH, from left to right.

    NOTE: The Azure CLI installs the version of Python it uses at:

  2. Get the path of a program:

    which python

    The response is


    because the file “python” in actually a symbolic link to the actual executable.

    System Python2

    Although earlier macOS comes with a version of Python, it is Python2, which is now obsolete in favor of python3.

    Python 2 reached end of life in January 2020.

  3. List symbolic link “/usr/bin/python” for the default Python that comes with macOS Catalina:

    ls -l /usr/bin/python


    lrwxr-xr-x  1 root  wheel  75 Apr 15 03:55 /usr/bin/python -> ../../System/Library/Frameworks/Python.framework/Versions/2.7/bin/python2.7

    The “../../” above means that it’s above your HOME folder, in the root of macOS.

  4. So let’s see what files are there:

    ls /System/Library/Frameworks/Python.framework/Versions/2.7/bin/

    Listed are executables “python”, “python2”, and “python2.7”, plus others.

    View $PATH

  5. View the contents of the $PATH variable to see if it includes “/usr/bin/python”:

    echo $PATH

    If that’s too much, view it in a file by a text editor:

    echo "$PATH" >path.txt
    edit path.txt
    rm path.txt
  6. If it’s listed, invoke the program to get the version number:

    python2 --version

    On a MacOS Catalina version operating system, you would see:

    Python 2.7.16
  7. The version is also returned from:

    python2 -V

    The Python3 interpreter is the default Python now, but it needs to be installed.

  8. By the end of this tutorial, you would set “python” to Python 3, the current version when you do:

    python -V

    The response would be:

    Python 3.7.8

    Ways to Install Python3

    Before pyenv, people define what version of “python” is executed by editing the PATH variable – putting the path to Python3 executables before Python2 in $PATH. To have Python3 execute instead of Python2:

    export PATH="/usr/local/python3:$PATH"

    (Colon characters separate folders in the PATH)

    Alternately, Azure recommends this line at the bottom of ~/.bash_profile files:

    export PATH="/usr/local/opt/python@3.8/bin:$PATH"

    The line above overrides the system’s type command described above.

    Don’t alias with pyenv

    Some add aliases within the ~/.bash_profile or ~/.zshenv file:

    alias python=python3
    alias pip=pip3

    But don’t do that when you’re using pyenv, which makes use of shims.

    NOTE: pyenv is for macOS.

    Windows install

    On Windows, consider using @kirankotari’s pyenv-win fork at:

    Ubuntu install

    See https://amaral.northwestern.edu/resources/guides/pyenv-tutorial

    Brew Install Pyenv on Mac

  9. After installing the Homebrew package manager:

    brew install pyenv

    The response at time of this writing:

    ==> Installing dependencies for pyenv: openssl@1.1 and pkg-config
    ==> Installing pyenv dependency: openssl@1.1
    ==> Downloading https://homebrew.bintray.com/bottles/openssl@1.1-1.1.1g.catalina
    ==> Downloading from https://akamai.bintray.com/19/1926679569c6af5337de812d86f4d
    ==> Pouring openssl@1.1-1.1.1g.catalina.bottle.tar.gz
    ==> Caveats
    A CA file has been bootstrapped using certificates from the system
    keychain. To add additional certificates, place .pem files in
    and run
    openssl@1.1 is keg-only, which means it was not symlinked into /usr/local,
    because macOS provides LibreSSL.
    If you need to have openssl@1.1 first in your PATH run:
      echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.bash_profile
    For compilers to find openssl@1.1 you may need to set:
      export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
      export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"
    For pkg-config to find openssl@1.1 you may need to set:
      export PKG_CONFIG_PATH="/usr/local/opt/openssl@1.1/lib/pkgconfig"
    ==> Summary
    🍺  /usr/local/Cellar/openssl@1.1/1.1.1g: 8,059 files, 18MB
    ==> Installing pyenv dependency: pkg-config
    ==> Downloading https://homebrew.bintray.com/bottles/pkg-config-0.29.2_3.catalin
    ==> Downloading from https://akamai.bintray.com/80/80f141e695f73bd058fd82e9f539d
    ==> Pouring pkg-config-0.29.2_3.catalina.bottle.tar.gz
    Error: The `brew link` step did not complete successfully
    The formula built, but is not symlinked into /usr/local
    Could not symlink bin/pkg-config
    Target /usr/local/bin/pkg-config
    already exists. You may want to remove it:
      rm '/usr/local/bin/pkg-config'
    To force the link and overwrite all conflicting files:
      brew link --overwrite pkg-config
    To list all files that would be deleted:
      brew link --overwrite --dry-run pkg-config
    Possible conflicting files are:
    ==> Summary
    🍺  /usr/local/Cellar/pkg-config/0.29.2_3: 11 files, 623.7KB
    ==> Installing pyenv
    ==> Downloading https://homebrew.bintray.com/bottles/pyenv-1.2.18.catalina.bottl
    ==> Downloading from https://akamai.bintray.com/bd/bd9f719f153e9574dcc65dc7fea28
    ==> Pouring pyenv-1.2.18.catalina.bottle.tar.gz
    🍺  /usr/local/Cellar/pyenv/1.2.18: 695 files, 2.5MB
    ==> Caveats
    ==> openssl@1.1
    A CA file has been bootstrapped using certificates from the system
    keychain. To add additional certificates, place .pem files in
    and run
    openssl@1.1 is keg-only, which means it was not symlinked into /usr/local,
    because macOS provides LibreSSL.
    If you need to have openssl@1.1 first in your PATH run:
      echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.bash_profile
    For compilers to find openssl@1.1 you may need to set:
      export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
      export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"
    For pkg-config to find openssl@1.1 you may need to set:
      export PKG_CONFIG_PATH="/usr/local/opt/openssl@1.1/lib/pkgconfig"

    Notice that brew takes care of installing dependencies needed. That’s a huge benefit of using the brew package manager. Otherwise, you would have to install dependencies yourself before installing pyenv, which can be error-prone. See

    • https://realpython.com/intro-to-pyenv/ about Pyenv Build Dependencies.
    • https://github.com/pyenv/pyenv/wiki
    • https://github.com/pyenv/pyenv#basic-github-checkout

    Verify Install

  10. Can the program execute?

    pyenv --version

    At time of writing, the response was the version.release.path semantic version:

    pyenv 1.2.26

    TODO: Create variable containing “1.2.26”.

  11. Let’s ask where the operating system thinks pyenv is installed:

    which pyenv

    The response:


    /usr/local/bin/ is NOT owned by the operating system, so sudo is NOT required to modify files in it. That’s why installers such as Brew install programs there.

  12. Note that file /usr/local/bin/pyenv is a symbolic link which points to the actual executable (at bash script) in another folder:

    head -3 /usr/local/bin/pyenv 

    head -3 lists the first 3 lines of the file.

    The response #!/usr/bin/env bash is the “shebang” line which specifies that the text file be treated as a bash shell script.

  13. What is the latest version of the pyenv installer on Homebrew?

    brew info pyenv

    The response, at time of writing, several months after the initial install:

    pyenv: stable 1.2.26 (bottled), HEAD
    Python version management
    /usr/local/Cellar/pyenv/1.2.26 (747 files, 2.6MB) *
      Poured from bottle on 2021-04-06 at 10:17:17
    From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/pyenv.rb
    License: MIT
    ==> Dependencies
    Required: autoconf ✔, openssl@1.1 ✔, pkg-config ✔, readline ✔
    ==> Options
      Install HEAD version

    Previously in 1.2.19:

    install: 61,768 (30 days), 168,980 (90 days), 637,536 (365 days)
    install-on-request: 58,851 (30 days), 160,831 (90 days), 594,089 (365 days)
    build-error: 0 (30 days)
  14. To find the actual executable file which brew installed:

    ls -al /usr/local/bin/pyenv

    The response shows a link to where Homebrew stores its executable:

    lrwxr-xr-x  1 john_doe  admin  32 Apr  6 10:17 /usr/local/bin/pyenv -> ../Cellar/pyenv/1.2.26/bin/pyenv

    PROTIP: The “..” means “/usr/local/” should be added to make the full path:

    ls -al /usr/local/Cellar/pyenv/1.2.26/bin/pyenv

    lrwxr-xr-x  1 john_doe  staff  16 Apr  5 05:27 /usr/local/Cellar/pyenv/1.2.26/bin/pyenv -> ../libexec/pyenv

    pyenv Commands

  15. List sub-commands recognized by pyenv:


    At time of writing:

pyenv 1.2.26
Usage: pyenv <command> [<args>]
Some useful pyenv commands are:
   --version   Display the version of pyenv
   activate    Activate virtual environment
   commands    List all available pyenv commands
   deactivate   Deactivate virtual environment
   exec        Run an executable with the selected Python version
   global      Set or show the global Python version(s)
   help        Display help for a command
   hooks       List hook scripts for a given pyenv command
   init        Configure the shell environment for pyenv
   install     Install a Python version using python-build
   local       Set or show the local application-specific Python version(s)
   prefix      Display prefix for a Python version
   rehash      Rehash pyenv shims (run this after installing executables)
   root        Display the root directory where versions and shims are kept
   shell       Set or show the shell-specific Python version
   shims       List existing pyenv shims
   uninstall   Uninstall a specific Python version
   version     Show the current Python version(s) and its origin
   version-file   Detect the file that sets the current pyenv version
   version-name   Show the current Python version
   version-origin   Explain how the current Python version is set
   versions    List all Python versions available to pyenv
   virtualenv   Create a Python virtualenv using the pyenv-virtualenv plugin
   virtualenv-delete   Uninstall a specific Python virtualenv
   virtualenv-init   Configure the shell environment for pyenv-virtualenv
   virtualenv-prefix   Display real_prefix for a Python virtualenv version
   virtualenvs   List all Python virtualenvs found in `$PYENV_ROOT/versions/*'.
   whence      List all Python versions that contain the given executable
   which       Display the full path to an executable
See `pyenv help ' for information on a specific command.
For full documentation, see: https://github.com/pyenv/pyenv#readme
  1. For a full list of sub-commands recognized by pyenv:

    pyenv commands

    At time of writing:


    Where are pyenv shims installed?

    PROTIP: When pyenv is installed, a folder ~/.pyenv is created under your user $HOME folder.

  2. Reveal folders under the pyenv root containing the shims folder:

    ls $( pyenv root )

    The response shows that the installer created a folder at my user $HOME folder (but your user name instead of my “john_doe”):

    plugins		shims		version		versions 
  3. See the various versions of python (and associated utilities) we want to put at the top of the $PATH:

    ls $(pyenv root)/shims

    For example:

    2to3                     nltk                     python
    2to3-3.7                 pathy                    python-config
    2to3-3.8                 pbr                      python3
    __pycache__              pdf2txt.py               python3-config
    bandit                   pdfplumber               python3.7
    bandit-baseline          pip                      python3.7-config
    bandit-config-generator  pip3                     python3.7-gdb.py
    chardetect               pip3.7                   python3.7m
    distro                   pip3.8                   python3.7m-config
    dumppdf.py               pipenv                   python3.8
    easy_install             pipenv-resolver          python3.8-config
    easy_install-3.7         pkginfo                  python3.8-gdb.py
    easy_install-3.8         pydoc                    pyuic6
    estimator_ckpt_converter pydoc3                   pyvenv
    f2py                     pydoc3.7                 pyvenv-3.7
    f2py3                    pydoc3.8                 saved_model_cli
    f2py3.7                  pyfiglet                 spacy
    get_objgraph             pyi-archive_viewer       spark
    google-oauthlib-tool     pyi-bindepend            tabulate
    idle                     pyi-grab_version         tensorboard
    idle3                    pyi-makespec             tf_upgrade_v2
    idle3.7                  pyi-set_version          tflite_convert
    idle3.8                  pyinstaller              toco
    isort                    pylupdate6               toco_from_protos
    jake                     pyrsa-decrypt            tqdm
    latin2ascii.py           pyrsa-encrypt            undill
    macho_dump               pyrsa-keygen             virtualenv
    macho_find               pyrsa-priv2pub           virtualenv-clone
    macho_standalone         pyrsa-sign               wheel
    markdown_py              pyrsa-verify

    Within the shims folder are every Python command in every installed version of Python—python, pip, etc.

  4. Count files within the shims folder:

    find ~/.pyenv/shims -type f | wc -l


    How is Pyenv enabled?

  5. Edit your ~/.bash_profile and/or ~/.zshenv file so that the PATH comes before “/usr/local/bin” and “/usr/bin” where others install programs:

    # Set it so ~/.pyenv provides Python before others of the same name:
    export PYENV_ROOT=$(pyenv root)
    export PATH="$PYENV_ROOT/shims:$PATH"

    Put the above at the bottom of the file.

    Alternately, if you don’t want to mess with a text editor, use this command:

    echo 'export PATH="$(pyenv root)/bin:$PATH"' >> ~/.bash_profile

    Instead of .bash_profile, specify ~/.zshrc or ~/.bashrc (for Ubuntu/Fedora).

    $(pyenv root)/shims:/usr/local/bin:/usr/bin:/bin

    (Colon characters separate folders in the PATH)

  6. Whether you edited your shell script or not, restart your shell terminal so the path changes take effect:

    exec "$SHELL"
    source ~/.bash_profile


  7. Verify:

    echo "$PATH" >path.txt
    edit path.txt
    rm path.txt
  8. Confirm whether pyenv is now active:

    which python

    Response should be:


    (“john_doe” would be replaced with your user name)

    What Python Releases are available?

    Pyenv can install several releases of Python.

    PROTIP: Each version of Python is installed within pyenv’s versions folder:

    ls $( pyenv root)/versions
  9. If you haven’t already, install the tree utility:

    brew install tree
  10. List folders within the versions folder where pyenv installs versions of Python (for example):

    tree -d -L 1 $( pyenv root )/versions
    ├── 3.7.7
    ├── 3.7.9
    ├── 3.8.2
    └── 3.8.5

    None would be listed until versions are added.

  11. Limit the long list of Python Releases to 3.7 or 3.11, etc.:

    pyenv install --list | grep " 3\.[789]"

    At time of writing:



PROTIP: Select the last patch number of a release, such as “3.7.7” or “3.8.2” in the example above.

-dev releases are the ones under active development (changes).

Install Python using pyenv

  1. Navigate to the folder containing your Python .py file.

  2. To install a specific version of Python (selected from the response above):

    pyenv install 3.7.7
    python-build: use openssl@1.1 from homebrew
    python-build: use readline from homebrew
    Downloading Python-3.7.7.tar.xz...
    -> https://www.python.org/ftp/python/3.7.7/Python-3.7.7.tar.xz
    Installing Python-3.7.7...
    python-build: use readline from homebrew
    python-build: use zlib from xcode sdk
    Installed Python-3.7.7 to /Users/john_doe/.pyenv/versions/3.7.7

    Note that pyenv installs under a versions folder.

  3. Install another:

    pyenv install 3.8.2
    python-build: use openssl@1.1 from homebrew
    python-build: use readline from homebrew
    Downloading Python-3.8.2.tar.xz...
    -> https://www.python.org/ftp/python/3.8.2/Python-3.8.2.tar.xz
    Installing Python-3.8.2...
    python-build: use readline from homebrew
    python-build: use zlib from xcode sdk
    Installed Python-3.8.2 to /Users/john_doe/.pyenv/versions/3.8.2
  4. List the versions of Python installed and managed by pyenv:

    pyenv versions

    In the response such as:

    * 3.7.7 (set by /Users/john_doe/.python-version)

    * indicates the current system Python release.

    Set Version of Python using Pyenv

  5. To set a specific version as the default:

    pyenv global 3.7.9

    No response is returned if successful.

  6. To select a specific version:

    pyenv local 3.7.7

    No response is returned if successful.

    PROTIP: The pyenv local command above creates in the folder a hidden file named .python-version containing the Python version specified in the command.

  7. Verify what version.release.patch:

    python --version

    Order of Path override

    When a shim (such as “python”) is executed, it calls pyenv which determines which Python release to use by reading sources in this order:

    1. $PYENV_VERSION environment variable
    2. pyenv local x.y.z
    3. pyenv global x.y.z

    This means the PYENV_VERSION environment variable overrides all other specifications.

  8. Set this environment variable in your current shell session using the pyenv shell command.

    If the PYENV_VERSION system variable is not specified, the application-specific .python-version file in the current directory.

  9. Modify the current directory’s .python-version file with the command:

    pyenv local 2.7.15

    If there is no .python-version file locally, parent directories are searched upward until reaching the root of your filesystem.

    If no .python-version is specified in any folder, the global $(pyenv root)/version file.

  10. Modify the global file using the command:

    pyenv global 3.7.7

    If the global version file is not present, pyenv assumes you want to use the “system” Python. (In other words, whatever version would run if pyenv weren’t in your PATH.)

Virtual Environments

Both Pipenv and Pipenv automatically create and manage a virtualenv for your projects.

But only Pipenv adds/removes packages from your Pipfile as you install/uninstall packages. Pipenv generates the Pipfile.lock file used to produce deterministic builds.

Pipenv enables always use the latest versions of dependencies, to minimize security risks arising from outdated components.

pyenv copies an entire Python installation every time a new pyenv version is created.

By contrast, virtualenv makes use of symbolic links, which decreases the size of each virtualenv.

There are two utilities to add virtualenv functionality to pyenv: pyenv-virtualenvwrapper (described by https://alysivji.github.io/setting-up-pyenv-virtualenvwrapper.html) is less convenient and has less stars than the pyenv-virtualenv plugin. To install it:

brew install pyenv-virtualenv


git clone https://github.com/pyenv/pyenv-virtualenv.git \
   $(pyenv root)/plugins/pyenv-virtualenv
source ~/.bashrc

The response:

Cloning into '/Users/john_doe/.pyenv/plugins/pyenv-virtualenv'...
remote: Enumerating objects: 2064, done.
remote: Total 2064 (delta 0), reused 0 (delta 0), pack-reused 2064
Receiving objects: 100% (2064/2064), 580.31 KiB | 30.00 KiB/s, done.
Resolving deltas: 100% (1413/1413), done.

This is needed because Virtualenv and Anaconda also activate scripts by mutating $PATH variable of user’s interactive shell, which intercepts pyenv’s shim style command execution hooks.

To automatically activate/deactivate virtualenvs on entering/leaving directories which contain a .python-version file that contains the name of a valid virtual environment as shown in the output of pyenv virtualenvs

echo ‘eval “$(pyenv virtualenv-init -)”’ » ~/.bash_profile or ~/.zshrc

  1. List what virtualenv has been defined

    pyenv virtualenvs
  2. List what virtualenv have been defined:

    pyenv virtualenvs

    Setup Virtual environments

    There are several ways to enter and setup a virtual environment:

  3. Navigate to where you .py files are. This is important.

  4. Set up a virtual environment in the present working directory, using a common conventional name:

    python -m venv venv

    No response is returned if it’s all good.

    The above creates a virtual folder named after the project.

    To deactivate, type:


    Alternately, to activate virtualenv, run:

    pipenv shell

    Alternatively, run a command inside the virtualenv

    pipenv run
  5. Activate the virtual environment in the present working directory:

    source ./venv/bin/activate

    This would turn the command prompt as a constant reminder:


  6. which python

    /Users/... my_project/venv/bin/python


    The Pipfile.lock file contains a hash of each package installed, which provides security. The file pins semantic versions of all dependencies and sub-dependencies, which provides replicable environments.

Upgrade Pyenv

If you see this error:

    /Users/.../.pyenv/shims/python3: line 21: /usr/local/Cellar/pyenv/1.2.20/libexec/pyenv: No such file or directory
    brew upgrade pyenv

References for pyenv





References for pipenv

https://github.com/VaultVulp/pipenv-alpine/blob/master/Dockerfile is the Python Alpine image with pre-installed pipenv

  • https://stackoverflow.com/questions/58300046/how-to-make-lightweight-docker-image-for-python-app-with-pipenv
  • https://github.com/Ilhicas/alpine-pipenv

More about Python

This is one of a series about Python:

  1. Python install on MacOS
  2. Python install on MacOS using Pyenv
  3. Python install on Raspberry Pi for IoT

  4. Python tutorials
  5. Python Examples
  6. Python coding notes
  7. Pulumi controls cloud using Python, etc.
  8. Jupyter Notebooks provide commentary to Python

  9. Python certifications

  10. Test Python using Pytest BDD Selenium framework
  11. Test Python using Robot testing framework
  12. Testing AI uses Python code

  13. Microsoft Azure Machine Learning makes use of Python

  14. Python REST API programming using the Flask library
  15. Python coding for AWS Lambda Serverless programming
  16. Streamlit visualization framework powered by Python
  17. Web scraping using Scrapy, powered by Python
  18. Neo4j graph databases accessed from Python

More on OSX

This is one of a series on Mac OSX: