Published on

Python Virtual Environments - The Right Way to Manage Dependencies

Authors

Introduction

Every Python developer has been there: you install a package for one project, it breaks another project. Virtual environments solve this problem by giving each project its own isolated Python environment.

In 2026, there's also the blazing-fast uv tool to modernize your workflow.

What is a Virtual Environment?

A virtual environment is an isolated Python installation with its own packages. Changes to one venv don't affect others or your system Python.

System Python → has package A v1.0
Project 1 venv → has package A v1.0, package B v2.5
Project 2 venv → has package A v3.0, package C v1.2

Each project is completely isolated.

Creating and Using venv

# Create a virtual environment
python -m venv venv

# Activate it
source venv/bin/activate         # macOS/Linux
venv\Scripts\activate.bat        # Windows CMD
venv\Scripts\Activate.ps1        # Windows PowerShell

# Your prompt shows (venv) when active
(venv) $

# Install packages (only affects this venv)
pip install requests flask

# Deactivate
deactivate

Requirements Files

# Save current environment
pip freeze > requirements.txt

# Install from requirements.txt (on another machine/in CI)
pip install -r requirements.txt
requirements.txt
Flask==3.0.0
requests==2.31.0
SQLAlchemy==2.0.23
python-dotenv==1.0.0

Modern Approach: uv (Ultra-fast Package Manager)

uv is a new Python package manager written in Rust — 10-100x faster than pip:

# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh

# Create a project with uv
uv init my-project
cd my-project

# Add dependencies
uv add requests flask pandas

# Run your script (auto-installs dependencies)
uv run main.py

# Create a virtual environment explicitly
uv venv

# Sync dependencies from pyproject.toml
uv sync

uv manages everything in pyproject.toml — the modern Python standard:

pyproject.toml
[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
    "requests>=2.31.0",
    "flask>=3.0.0",
    "pandas>=2.1.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0.0",
    "black>=23.0.0",
    "mypy>=1.0.0",
]

Project Structure Best Practices

my-project/
├── venv/Virtual environment (never commit!)
├── src/
│   └── my_project/
│       ├── __init__.py
│       └── main.py
├── tests/
│   └── test_main.py
├── .gitignoreInclude venv/ here
├── pyproject.tomlModern: replaces setup.py + requirements.txt
└── README.md
.gitignore
venv/
.venv/
__pycache__/
*.pyc
.env
*.egg-info/
dist/
build/

Managing Different Python Versions with pyenv

# Install pyenv (macOS/Linux)
brew install pyenv

# Install a specific Python version
pyenv install 3.12.0
pyenv install 3.11.5

# Set global default
pyenv global 3.12.0

# Set project-specific version (creates .python-version file)
pyenv local 3.11.5

# List installed versions
pyenv versions

Environment Variables with .env

pip install python-dotenv
main.py
from dotenv import load_dotenv
import os

load_dotenv()  # Load .env file

DATABASE_URL = os.getenv('DATABASE_URL')
SECRET_KEY = os.getenv('SECRET_KEY')
DEBUG = os.getenv('DEBUG', 'false').lower() == 'true'
.env
DATABASE_URL=postgresql://user:pass@localhost/mydb
SECRET_KEY=super-secret-key-change-this
DEBUG=true

Conclusion

Virtual environments are non-negotiable for serious Python development. Use venv for simplicity, uv for speed, and pyenv when you need multiple Python versions. Always commit your requirements.txt or pyproject.toml, never your venv/ folder, and keep your .env in .gitignore. These habits will save you from countless "it works on my machine" headaches.