Python Virtual Environments — The Right Way to Manage Dependencies
Advertisement
Introduction
Why This Matters
Every professional Python project needs a virtual environment. Without one, all packages install globally, leading to version conflicts between projects, polluted system Python, and "works on my machine" deployment failures. A virtual environment creates an isolated Python installation per project, so each project has its own dependency tree.
In 2026, the Python tooling landscape has expanded significantly. venv + pip remain the baseline, but uv — a blazing-fast Rust-based package manager — has become the go-to choice for speed-conscious teams, and poetry continues to be popular for projects that need lockfiles and publishing. Understanding all of these makes you adaptable across codebases.
Virtual environments are also the first step toward containerization — Docker Python images mirror what virtual environments enforce: a clean, reproducible Python runtime with pinned dependencies.
venv — Built-In Virtual Environment Tool
# Create a virtual environment
python -m venv .venv
# Activate on macOS/Linux
source .venv/bin/activate
# Activate on Windows
.venv\Scripts\activate
# Verify
which python # should point to .venv/bin/python
python --version # Python 3.12.x
# Deactivate
deactivatepip — Installing Packages
# Install a package
pip install fastapi
# Install specific version
pip install fastapi==0.115.0
# Install minimum version
pip install "fastapi>=0.100.0"
# Install from requirements file
pip install -r requirements.txt
# Upgrade a package
pip install --upgrade fastapi
# Uninstall
pip uninstall fastapi
# List installed packages
pip list
# Show details of a package
pip show fastapirequirements.txt
# Generate from current environment
pip freeze > requirements.txt# requirements.txt
fastapi==0.115.0
uvicorn==0.30.0
pydantic==2.7.0
httpx==0.27.0
sqlalchemy==2.0.30# Install from file
pip install -r requirements.txtrequirements structure for dev dependencies
# requirements.txt — production deps
fastapi==0.115.0
uvicorn==0.30.0
pydantic==2.7.0
# requirements-dev.txt — dev deps
-r requirements.txt
pytest==8.2.0
mypy==1.10.0
ruff==0.4.0
black==24.4.0uv — The Fast Modern Package Manager
uv is a Rust-based Python package manager that is 10-100x faster than pip.
# Install uv
pip install uv
# or: curl -LsSf https://astral.sh/uv/install.sh | sh
# Create a new project
uv init myproject
cd myproject
# Add dependencies
uv add fastapi uvicorn
uv add --dev pytest mypy ruff
# Run a command in the environment
uv run python main.py
uv run pytest
# Sync dependencies from lock file
uv sync
# Generate lockfile
uv lockuv creates a pyproject.toml and uv.lock file for reproducible installs.
pyproject.toml — Modern Project Configuration
[project]
name = "myapp"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.115.0",
"uvicorn>=0.30.0",
"pydantic>=2.7.0",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0",
"mypy>=1.10",
"ruff>=0.4",
]
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.mypy]
strict = true
python_version = "3.11"poetry — Alternative Package Manager
# Install poetry
pip install poetry
# Create a new project
poetry new myproject
cd myproject
# Add dependency
poetry add fastapi
# Add dev dependency
poetry add --dev pytest
# Install all dependencies
poetry install
# Run in virtual env
poetry run python main.py
poetry run pytest
# Export to requirements.txt
poetry export -f requirements.txt --output requirements.txt.gitignore for Python Projects
# .gitignore
.venv/
__pycache__/
*.pyc
*.pyo
*.egg-info/
dist/
build/
.mypy_cache/
.pytest_cache/
.ruff_cache/
*.env
.DS_StoreEnvironment Variables with python-dotenv
pip install python-dotenv# .env file (never commit to git!)
DATABASE_URL=postgresql://user:pass@localhost/mydb
API_KEY=your-secret-key
DEBUG=true# main.py
from dotenv import load_dotenv
import os
load_dotenv() # loads .env into os.environ
db_url = os.environ["DATABASE_URL"]
debug = os.getenv("DEBUG", "false").lower() == "true"Common Mistakes
- Installing packages globally instead of inside a virtual environment
- Committing
.venv/to git — it is OS-specific and can be huge - Not pinning versions in
requirements.txt— causes non-reproducible builds - Forgetting to activate the virtual environment before running scripts
- Having multiple virtual environment directories (
.venv,env,venv) and confusing them
Best Practices
- Always name your virtual environment
.venv— it is the standard and gitignore rules cover it - Use
uvfor new projects — it is dramatically faster than pip - Pin exact versions in production; use ranges in libraries
- Store all dev tooling config (mypy, ruff, pytest) in
pyproject.toml - Never store secrets in code — always use environment variables with
python-dotenv
Key Takeaways
- Virtual environments isolate project dependencies, preventing version conflicts between projects
python -m venv .venvcreates a virtual environment;source .venv/bin/activateactivates itpip freeze > requirements.txtcaptures the exact installed package versionsuvis the fastest Python package manager in 2026 — 10-100x faster than pippyproject.tomlis the modern standard for project configuration, replacingsetup.py- Never commit
.venv/or.envto git — add them to.gitignore - Use separate
requirements.txtandrequirements-dev.txtto separate production from dev dependencies
Advertisement