Skip to content
yisusvii Blog
Go back

Managing Python Versions with pyenv and venv on Linux, Mac, and Windows

Suggest Changes

Table of Contents

Open Table of Contents

Why Python Version Management Matters

Modern Python development rarely happens in a vacuum. You might be maintaining a legacy service pinned to Python 3.9 while simultaneously prototyping with the brand-new Python 3.15 feature set. Without proper tooling, juggling these versions turns into a fragile mess of manual symlinks, conflicting system packages, and “it works on my machine” conversations.

Two tools handle this cleanly:

Together they give you a reproducible, portable local setup on Linux, macOS, and Windows.


Understanding the Two Layers

Before diving into installation, it helps to separate what each tool does:

ToolManagesScope
pyenvPython interpreter versionsSystem-wide or per directory
venvPackage dependenciesPer project

pyenv answers: “Which Python binary am I using?” venv answers: “Which packages are installed for this project?”

They complement each other. pyenv selects the interpreter; venv isolates the packages on top of that interpreter.


Linux Setup

1. Install pyenv

The official installer script handles all dependencies:

curl https://pyenv.run | bash

After the script finishes, add the following to your shell configuration file (~/.bashrc, ~/.zshrc, or ~/.profile):

export PYENV_ROOT="$HOME/.pyenv"
[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

Reload your shell:

exec "$SHELL"

2. Install Build Dependencies

pyenv compiles Python from source. Install the required build tools first:

Debian / Ubuntu:

sudo apt update && sudo apt install -y \
  build-essential libssl-dev zlib1g-dev \
  libbz2-dev libreadline-dev libsqlite3-dev \
  libncursesw5-dev xz-utils tk-dev \
  libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev

Fedora / RHEL / CentOS:

sudo dnf install -y \
  gcc make openssl-devel bzip2-devel \
  libffi-devel readline-devel sqlite-devel \
  zlib-devel xz-devel tk-devel

Arch Linux:

sudo pacman -S --needed base-devel openssl zlib xz tk

macOS Setup

1. Install Homebrew (if not already installed)

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

2. Install pyenv via Homebrew

brew update
brew install pyenv

Add the shell integration to your profile (~/.zshrc for Zsh, which is the default since macOS Catalina):

echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc
echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(pyenv init -)"' >> ~/.zshrc

Restart your terminal or run:

source ~/.zshrc

Note for Apple Silicon (M1/M2/M3/M4)

Homebrew installs natively for ARM. pyenv compiles Python for ARM by default, which is correct. If you need an x86 Python for legacy compatibility, use Rosetta 2 and an x86 Homebrew installation — but this is rarely needed for modern Python versions.


Windows Setup

pyenv does not run natively on Windows. The community project pyenv-win provides equivalent functionality.

1. Install pyenv-win via PowerShell

Open PowerShell as a regular user (not Administrator) and run:

Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/pyenv-win/pyenv-win/master/pyenv-win/install-pyenv-win.ps1" -OutFile "./install-pyenv-win.ps1"
& "./install-pyenv-win.ps1"

If you hit an execution policy error:

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Then re-run the install script.

2. Verify Environment Variables

After installation, confirm these variables exist (the installer sets them automatically):

[System.Environment]::GetEnvironmentVariable("PYENV", "User")
[System.Environment]::GetEnvironmentVariable("PYENV_ROOT", "User")
[System.Environment]::GetEnvironmentVariable("PYENV_HOME", "User")

And confirm %PYENV%\bin and %PYENV%\shims are in your PATH:

echo $env:PATH

3. Restart Your Shell

Close and reopen PowerShell (or Windows Terminal) for the PATH changes to take effect.

pyenv --version

Installing Python Versions with pyenv

Once pyenv is installed on any platform, the workflow is identical.

List Available Python Versions

pyenv install --list

The list is long. Filter for a specific release line:

pyenv install --list | grep "3.15"

Install Python 3.15

pyenv install 3.15.0

pyenv downloads the source, compiles it, and places it in ~/.pyenv/versions/3.15.0/. This takes 1–3 minutes depending on your machine.

Install Additional Versions

You can install as many versions as disk space allows:

pyenv install 3.11.12
pyenv install 3.12.10
pyenv install 3.13.3
pyenv install 3.15.0

View Installed Versions

pyenv versions

Output example:

  system
  3.11.12
  3.12.10
  3.13.3
* 3.15.0 (set by /home/user/.pyenv/version)

The asterisk marks the currently active version.


Switching Python Versions

Globally (System Default)

Sets the default Python for your entire user session:

pyenv global 3.15.0
python --version
# Python 3.15.0

Per Directory (Project-Scoped)

Creates a .python-version file in the current directory. pyenv automatically activates this version whenever you enter the directory:

cd ~/projects/my-service
pyenv local 3.11.12
python --version
# Python 3.11.12

Check the directory and its .python-version file:

cat .python-version
# 3.11.12

Temporarily (Shell Session Only)

Override for the current terminal session without touching any files:

pyenv shell 3.13.3
python --version
# Python 3.13.3

Switching Python Versions on an Existing Project

This is the most common point of confusion. A virtual environment bakes the Python interpreter path into it at creation time — you cannot change the Python version of an existing venv in place. When you need a different Python version on a project that already has a .venv, follow these steps:

Step-by-step: changing the Python version for an existing project

# 1. Activate the current environment (if not already active)
source .venv/bin/activate        # Linux/macOS
.venv\Scripts\Activate.ps1       # Windows PowerShell

# 2. Export all currently installed packages before destroying anything
pip freeze > requirements.txt

# 3. Deactivate the environment
deactivate

# 4. Delete the old virtual environment
rm -rf .venv                     # Linux/macOS
Remove-Item -Recurse -Force .venv  # Windows PowerShell

# 5. Switch to the target Python version with pyenv
pyenv local 3.13.3               # replace with your target version

# 6. Verify the new version is active
python --version
# Python 3.13.3

# 7. Create a fresh virtual environment with the new interpreter
python -m venv .venv

# 8. Activate the new environment
source .venv/bin/activate        # Linux/macOS
.venv\Scripts\Activate.ps1       # Windows PowerShell

# 9. Restore your packages
pip install --upgrade pip
pip install -r requirements.txt

# 10. Confirm everything is working
python --version
pip list

Why you can’t skip the delete step

When python -m venv .venv runs, it writes the absolute path of the current Python binary into .venv/bin/python (or .venv/Scripts/python.exe on Windows). That symlink or copy does not update when you run pyenv local later. The only way to change the interpreter is to delete the old .venv and create a new one.

Checking which Python a venv was built with

# Linux/macOS
.venv/bin/python --version

# Windows
.venv\Scripts\python.exe --version

Run this before switching if you need to confirm what version was originally used.


Creating Virtual Environments with venv

venv is part of Python’s standard library since Python 3.3 — no additional installation needed.

Create a Virtual Environment

cd ~/projects/my-project
python -m venv .venv

This creates a .venv/ directory containing a copy of the Python interpreter and an isolated pip.

Convention: Name the virtual environment .venv — most editors (VS Code, PyCharm) auto-detect this name and activate it automatically.

Activate the Environment

Linux / macOS:

source .venv/bin/activate

Windows (Command Prompt):

.venv\Scripts\activate.bat

Windows (PowerShell):

.venv\Scripts\Activate.ps1

After activation, your prompt shows the environment name:

(.venv) $

Verify You Are Using the Right Python

which python        # Linux/macOS
where python        # Windows
python --version

Install Packages

pip install requests fastapi uvicorn

Packages install only inside .venv, never touching the system or other projects.

Freeze Dependencies

pip freeze > requirements.txt

Recreate the Environment Elsewhere

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Deactivate

deactivate

Packages persist between activate/deactivate cycles

This is important: deactivating a venv does not delete your installed packages. The packages live on disk inside .venv/lib/pythonX.Y/site-packages/ and are still there the next time you activate. You do not need to reinstall anything just because you deactivated.

# First session
source .venv/bin/activate
pip install requests fastapi
deactivate

# Later — same machine, same .venv directory
source .venv/bin/activate
pip list          # requests and fastapi are still there

The only time packages disappear is when you delete and recreate the .venv directory (for example, when switching Python versions as described above). In that case, use requirements.txt to restore them in one command:

pip install -r requirements.txt

Combining pyenv and venv: Full Workflow Example

Here is the complete workflow for starting a new Python 3.15 project:

# 1. Confirm Python 3.15 is installed
pyenv versions

# 2. Create project directory
mkdir ~/projects/python315-demo
cd ~/projects/python315-demo

# 3. Pin Python 3.15 for this directory
pyenv local 3.15.0

# 4. Verify
python --version
# Python 3.15.0

# 5. Create a virtual environment using Python 3.15
python -m venv .venv

# 6. Activate
source .venv/bin/activate     # Linux/macOS
# or
.venv\Scripts\Activate.ps1    # Windows PowerShell

# 7. Install dependencies
pip install --upgrade pip
pip install httpx pydantic

# 8. Freeze
pip freeze > requirements.txt

# 9. Work on your project...

# 10. Deactivate when done
deactivate

What’s New in Python 3.15

Python 3.15 (released October 2025) introduces several notable changes worth knowing when setting up a new environment:

Always consult the official Python 3.15 release notes before upgrading production workloads.


Useful pyenv Commands Reference

CommandDescription
pyenv install --listList all installable versions
pyenv install 3.15.0Install a specific version
pyenv uninstall 3.11.12Remove an installed version
pyenv versionsList all installed versions
pyenv versionShow the currently active version
pyenv global 3.15.0Set global default version
pyenv local 3.12.10Set version for current directory
pyenv shell 3.13.3Set version for current shell only
pyenv which pythonShow path to active Python binary
pyenv rehashRebuild shims (run after installing packages with binaries)

Useful venv Commands Reference

CommandDescription
python -m venv .venvCreate a virtual environment
source .venv/bin/activateActivate (Linux/macOS)
.venv\Scripts\Activate.ps1Activate (Windows PowerShell)
deactivateDeactivate the current environment
pip install <package>Install a package into the active env
pip freeze > requirements.txtExport installed packages
pip install -r requirements.txtInstall from requirements file
pip listList installed packages
pip show <package>Show package details

Common Pitfalls

“python” still points to system Python after installing pyenv Ensure the pyenv shims directory is at the front of your PATH. Run pyenv doctor to diagnose configuration issues.

Virtual environment was created with the wrong Python version Delete .venv/ and recreate it after running pyenv local <version>. The Python version baked into a venv cannot be changed in place.

pip installs to system Python instead of venv Make sure you activated the venv. Your prompt should show (.venv). Run which pip (Linux/macOS) or where pip (Windows) to confirm.

Packages are missing every time I activate the venv Packages persist inside .venv/ and do not disappear on deactivation. If packages are consistently missing on activate, one of these is happening: (a) you are accidentally activating a different .venv in a parent directory, (b) something is deleting and recreating .venv (e.g., a CI script or an IDE setting), or (c) you ran python -m venv .venv again, which overwrites the directory. Run pip list right after activating and compare to ls .venv/lib/ to confirm the environment’s contents. If you need to restore packages, run pip install -r requirements.txt.

Windows: “cannot be loaded because running scripts is disabled” Run Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser in PowerShell before activating.

pyenv-win: Python installed but not recognized Run pyenv rehash after installation to regenerate shims, then open a new terminal window.


Summary

This setup keeps your local machine clean, your projects reproducible, and your Python versions organized regardless of whether you are on Linux, macOS, or Windows.


Suggest Changes
Share this post on:

Previous Post
The Most Cost-Effective Security Stack in 2026 for Azure Kubernetes With ACR and GitHub Actions CI/CD
Next Post
The Ultimate Guide to Playwright Website Testing