Virtual Environments & pip

Difficulty: Beginner

Question

Why are virtual environments important? How do you manage Python dependencies?

Answer

Virtual environments create isolated Python installations with their own packages, preventing version conflicts between projects.

Without virtual environments: - Project A needs Django 4.0, Project B needs Django 3.2 - conflict! - System Python gets polluted with project-specific packages

Tools: - venv (built-in): creates lightweight virtual environments - pip: installs packages from PyPI - requirements.txt: lists pinned dependencies - pyproject.toml: modern project configuration (PEP 621)

Modern alternatives: Poetry, PDM, uv (fast Rust-based installer).

Code examples

Creating and Using Virtual Environments

# Create a virtual environment
# python -m venv myproject_env

# Activate it
# Windows: myproject_env\Scripts\activate
# macOS/Linux: source myproject_env/bin/activate

# Now pip installs go to the venv, not system Python
# pip install requests flask

# Check where Python and packages live
import sys
print(f"Python: {sys.executable}")
print(f"Path: {sys.prefix}")

# List installed packages
# pip list
# pip freeze > requirements.txt

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

# Deactivate
# deactivate

# Example requirements.txt
requirements = """
flask==3.0.0
requests>=2.28.0,<3.0.0
numpy~=1.24.0
python-dotenv
"""
print(requirements)

# Version specifiers:
# ==3.0.0  exact version
# >=2.28   minimum version
# <3.0.0   maximum version
# ~=1.24   compatible release (>=1.24, <2.0)
# !=1.5    exclude version

Virtual environments isolate dependencies per project. pip freeze captures exact versions for reproducibility. Always commit requirements.txt to version control.

Modern Project Setup with pyproject.toml

# pyproject.toml (PEP 621) - modern Python project config
pyproject = """
[project]
name = "mypackage"
version = "1.0.0"
description = "My awesome package"
requires-python = ">=3.9"
dependencies = [
    "requests>=2.28",
    "flask>=3.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "black>=23.0",
    "mypy>=1.0",
]

[tool.pytest.ini_options]
testpaths = ["tests"]

[tool.black]
line-length = 88

[tool.mypy]
strict = true
"""
print(pyproject)

# Install with optional dependencies:
# pip install -e .           (just main deps)
# pip install -e ".[dev]"    (main + dev deps)

# pip-tools for pinning:
# pip-compile pyproject.toml -> requirements.txt
# pip-sync requirements.txt

pyproject.toml replaces setup.py/setup.cfg as the standard project configuration. It configures the project metadata, dependencies, and tool settings in one file.

Dependency Management Best Practices

# 1. Always use virtual environments
# python -m venv .venv
# source .venv/bin/activate

# 2. Pin dependencies for production
# requirements.txt (pinned)
# flask==3.0.0
# requests==2.31.0
# gunicorn==21.2.0

# 3. Separate dev and prod dependencies
# requirements.txt       -> production
# requirements-dev.txt   -> development (includes testing, linting)

# requirements-dev.txt
dev_requirements = """
-r requirements.txt
pytest==7.4.0
black==23.7.0
flake8==6.1.0
mypy==1.5.0
"""
print(dev_requirements)

# 4. Check for vulnerabilities
# pip audit
# safety check

# 5. Modern tools comparison
tools = {
    'pip + venv': 'Built-in, simple, manual pinning',
    'Poetry': 'All-in-one: venv, deps, building, publishing',
    'PDM': 'PEP 621 compliant, fast, modern',
    'uv': 'Rust-based, extremely fast pip replacement',
    'pip-tools': 'pip-compile + pip-sync for pinning',
}
for tool, desc in tools.items():
    print(f"{tool:15} -> {desc}")

Pin exact versions for production reproducibility. Use pip-compile or Poetry lock files to generate pinned requirements from loose constraints.

Key points

Concepts covered

venv, pip, requirements.txt, pyproject.toml, Dependency Management