Wilson Mar bio photo

Wilson Mar

Hello. Hire me!

Email me Calendar Skype call 310 320-7878

LinkedIn Twitter Gitter Instagram Youtube

Github Stackoverflow Pinterest

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

US (English)   Español (Spanish)   Français (French)   Deutsch (German)   Italiano   Português   Cyrillic Russian   中文 (简体) 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 enviornment also managed by pyenv.

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

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.

What is the magic of pyenv?

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 setting the shim programs to be the first folder in your PATH because the operating system searches for executables in folders in the PATH from left to right.

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 only:

    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 /usr/local/ which does NOT require sudo to access.

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

    python is /Users/wilson_mar/.pyenv/shims/python
    python is /Users/wilson_mar/anaconda3/bin/python
    python3 is /usr/local/opt/python@3.8/bin/python3
    python is /usr/local/anaconda3/bin/python
    python is /usr/bin/python

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


    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.

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

    python3 is /Users/wilson_mar/.pyenv/shims/python3
  2. Get the path of a program:

    which python

    The response is always


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

    System Python2

    Although 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
    ######################################################################## 100.0%
    ==> 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
    ######################################################################## 100.0%
    ==> 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
    ######################################################################## 100.0%
    ==> 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.20
  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 pyenv?

    brew info pyenv

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

    pyenv: stable 1.2.20 (bottled), HEAD
    Python version management
    /usr/local/Cellar/pyenv/1.2.20 (708 files, 2.5MB) *
      Poured from bottle on 2020-07-28 at 16:54:33
    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
    ==> Analytics
    install: 68,833 (30 days), 178,833 (90 days), 650,651 (365 days)
    install-on-request: 65,514 (30 days), 169,985 (90 days), 606,662 (365 days)
    build-error: 0 (30 days)

    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 wilson_mar  admin  32 Apr 24 21:55 /usr/local/bin/pyenv -> ../Cellar/pyenv/1.2.18/bin/pyenv

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

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

    lrwxr-xr-x  1 wilson_mar  admin  32 Jul 28 16:54 /usr/local/bin/pyenv -> ../Cellar/pyenv/1.2.20/bin/pyenv

    pyenv Commands

  15. List sub-commands recognized by pyenv:


    At time of writing:

pyenv 1.2.20
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
sed: RE error: illegal byte sequence
   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 “wilson_mar”):

    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                     pbr                      python3-config
    2to3-3.7                 pip                      python3.7
    2to3-3.8                 pip3                     python3.7-config
    bandit                   pip3.7                   python3.7-gdb.py
    bandit-baseline          pip3.8                   python3.7m
    bandit-config-generator  pipenv                   python3.7m-config
    chardetect               pipenv-resolver          python3.8
    easy_install             pydoc                    python3.8-config
    easy_install-3.7         pydoc3                   python3.8-gdb.py
    easy_install-3.8         pydoc3.7                 pyvenv
    estimator_ckpt_converter pydoc3.8                 pyvenv-3.7
    f2py                     pyrsa-decrypt            saved_model_cli
    f2py3                    pyrsa-encrypt            tensorboard
    f2py3.7                  pyrsa-keygen             tf_upgrade_v2
    google-oauthlib-tool     pyrsa-priv2pub           tflite_convert
    idle                     pyrsa-sign               toco
    idle3                    pyrsa-verify             toco_from_protos
    idle3.7                  python                   virtualenv
    idle3.8                  python-config            virtualenv-clone
    markdown_py              python3                  wheel

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

  4. Recursively count files (-type f) within the shims folder:

    ls ~/.pyenv/shims
    * system (set by /Users/wilson_mar/.pyenv/version)
    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/bin:$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:


    (“wilson_mar” 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. 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.8.2

    None would be listed until versions are added.

  10. Limit the long list of Python Releases to 3.7 or 3.8 or 3.9:

    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/wilson_mar/.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/wilson_mar/.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/wilson_mar/.pyenv/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.8.2

    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

    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 creates and manages 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/wilson_mar/.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 enviornments

    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</pip>
    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.

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 using Pyenv
  2. Python install on MacOS
  3. Python install on Raspberry Pi for IoT

  4. Test Python using Pytest BDD Selenium framework
  5. Test Python using Robot testing framework

  6. Python certifications
  7. Python tutorials
  8. Python coding notes
  9. Jupyter Notebooks provide commentary to Python

  10. Pulumi controls cloud using Python, etc.
  11. Microsoft Azure Machine Learning makes use of Python
  12. Testing AI uses Python code

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

More on OSX

This is one of a series on Mac OSX: