Python Virtual Environments

Isolate project dependencies with venv, manage packages with pip, juggle multiple Python versions with pyenv, and level up with Poetry.

Beginner

Why Virtual Environments

Without virtual environments, all projects share the same global Python installation. This causes problems:

Bash
# Project A needs requests 2.28
# Project B needs requests 2.31
# You can't have both installed globally!

# Also: installing packages globally requires admin/sudo
# and can conflict with system Python packages on Linux/macOS

Virtual environments solve this by giving each project its own isolated Python with its own packages:

Bash
project-a/
    .venv/          # has requests 2.28
    src/
    requirements.txt

project-b/
    .venv/          # has requests 2.31
    src/
    requirements.txt

venv Basics

Create and activate a virtual environment - the pattern is the same on all platforms:

Bash
# Create a virtual environment named .venv
python3 -m venv .venv

# Activate
source .venv/bin/activate

# Your prompt changes to show (.venv)
# (.venv) $ which python
# /path/to/project/.venv/bin/python

# Deactivate
deactivate
Bash
# Create
python -m venv .venv

# Activate (PowerShell)
.venv\Scripts\Activate.ps1

# Activate (cmd.exe)
.venv\Scripts\activate.bat

# Deactivate
deactivate

Specify a Python version when creating the environment:

Bash
# Use a specific Python version (must be installed)
python3.12 -m venv .venv

# Verify
python --version    # Python 3.12.x

# Create without pip (faster, add pip manually if needed)
python -m venv .venv --without-pip

# Create with system site-packages access
python -m venv .venv --system-site-packages
Name your environment .venv

The conventional name is .venv (or venv). Many tools (VS Code, PyCharm, GitHub Copilot) detect .venv automatically. Add it to .gitignore immediately: echo ".venv/" >> .gitignore. Never commit the virtual environment directory.

pip and Packages

With your virtual environment activated, pip installs into it - not the global Python:

Bash
# Install a package
pip install requests

# Install a specific version
pip install requests==2.31.0

# Install minimum version
pip install "requests>=2.28"

# Install multiple packages
pip install flask sqlalchemy pytest

# Upgrade a package
pip install --upgrade requests

# Uninstall
pip uninstall requests

# List installed packages
pip list

# Show package details
pip show requests

# Search PyPI (deprecated in pip 21+, use pypi.org instead)
# pip search requests
Bash
# Install from git repository
pip install git+https://github.com/user/repo.git

# Install from local directory (editable/development mode)
pip install -e .

# Install from a wheel file
pip install mypackage-1.0.0-py3-none-any.whl

# Upgrade pip itself
python -m pip install --upgrade pip

requirements.txt

requirements.txt records your project's dependencies so others can recreate the exact environment:

Bash
# Snapshot current environment (pinned versions)
pip freeze > requirements.txt

# Install from requirements.txt
pip install -r requirements.txt

# Install and upgrade all packages from file
pip install --upgrade -r requirements.txt

A well-organised requirements setup separates production and dev dependencies:

Bash
# requirements.txt - production dependencies
flask==3.0.3
sqlalchemy==2.0.30
requests==2.32.3
Bash
# requirements-dev.txt - dev/test dependencies
-r requirements.txt       # include production deps
pytest==8.2.1
pytest-cov==5.0.0
mypy==1.10.0
black==24.4.2
Bash
# Production install
pip install -r requirements.txt

# Development install
pip install -r requirements-dev.txt
pip-tools for manageable requirements

pip freeze includes ALL installed packages including transitive dependencies - making it hard to see which are your direct dependencies. pip-tools separates this: write direct deps in requirements.in, run pip-compile to produce a pinned requirements.txt. This makes upgrading individual packages easier.

pyenv - Multiple Python Versions

pyenv lets you install and switch between multiple Python versions on one machine:

Bash
# Install pyenv (macOS via Homebrew)
brew install pyenv

# Install pyenv (Linux)
curl https://pyenv.run | bash

# Add to shell profile (.bashrc / .zshrc)
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
Bash
# List available Python versions
pyenv install --list

# Install a specific Python version
pyenv install 3.12.4
pyenv install 3.11.9

# List installed versions
pyenv versions

# Set global Python version (affects all shells)
pyenv global 3.12.4

# Set local version for current directory (creates .python-version file)
pyenv local 3.11.9

# Set version just for current shell
pyenv shell 3.10.14

# Check active version
python --version
pyenv version

Combine pyenv with venv for per-project environments with specific Python versions:

Bash
cd my-project
pyenv local 3.12.4               # use Python 3.12 in this directory
python -m venv .venv             # create venv using the local Python
source .venv/bin/activate
python --version                 # Python 3.12.4

Poetry

Poetry is a modern dependency manager that combines venv creation, dependency resolution, and packaging in one tool:

Bash
# Install Poetry (Linux/macOS/WSL)
curl -sSL https://install.python-poetry.org | python3 -

# Windows (PowerShell)
(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | python -

# Create a new project
poetry new my-project

# Or initialise in an existing directory
cd my-project
poetry init

Poetry creates and manages pyproject.toml and poetry.lock:

Bash
# Add a dependency (updates pyproject.toml + poetry.lock)
poetry add requests
poetry add "requests>=2.28"

# Add a dev dependency
poetry add --group dev pytest mypy

# Install all dependencies (creates .venv automatically)
poetry install

# Install only production dependencies
poetry install --without dev

# Update all dependencies
poetry update

# Show installed packages
poetry show

# Run a command inside the venv
poetry run python script.py
poetry run pytest

Sample pyproject.toml managed by Poetry:

Python
[tool.poetry]
name = "my-project"
version = "0.1.0"
description = "A sample project"
authors = ["Alice "]

[tool.poetry.dependencies]
python = "^3.12"
requests = "^2.31"
click = "^8.1"

[tool.poetry.group.dev.dependencies]
pytest = "^8.0"
mypy = "^1.10"
black = "^24.0"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
ToolBest for
venv + pipSimple projects, scripts, learning - built-in, no extra install
pip-toolsProduction apps needing pinned transitive deps without Poetry overhead
PoetryLibraries and applications with publishing workflow
uvExtremely fast (Rust-based) drop-in for pip + venv (2024+)
pyenvManaging multiple Python versions on one machine

Frequently Asked Questions

A virtual environment is an isolated Python installation. It has its own python executable and site-packages directory. Packages installed in a virtual environment don't affect other environments or the system Python. This lets different projects use different package versions without conflict.

venv is built into Python 3.3+ - no installation required. virtualenv is a third-party package that predates venv, works with Python 2, and is faster to create environments. For new Python 3 projects, use venv. Install virtualenv only if you need Python 2 support or its additional features.

No. Add .venv/ and venv/ to your .gitignore. Virtual environments contain compiled binaries that are platform-specific and large. Instead, commit requirements.txt (or pyproject.toml with Poetry) so others can recreate the exact environment with pip install -r requirements.txt.

pip list shows installed packages in human-readable format. pip freeze outputs packages in requirements.txt format (package==version) suitable for saving to a file. Use pip freeze > requirements.txt to snapshot your environment. Note that pip freeze includes all packages including transitive dependencies - consider pip-tools for managing just direct dependencies.