Why Virtual Environments?
Virtual environments create isolated Python installations for each project. Without them, all projects share the same global packages, leading to version conflicts. For example, Project A might need Django 4.2 while Project B needs Django 5.0. Virtual environments solve this by giving each project its own package directory.
The Dependency Problem
Without virtual environments, installing packages globally leads to:
- Version conflicts: Two projects needing different versions of the same package
- Reproducibility issues: Cannot guarantee the same environment on another machine
- System pollution: Global packages cluttering your system Python
- Permission issues: Needing sudo to install packages globally
Creating and Using Virtual Environments
# Create a virtual environment using venv (built into Python 3.3+)
# python3 -m venv myproject-env
# Activate the virtual environment
# On macOS/Linux:
# source myproject-env/bin/activate
# On Windows:
# myproject-env\Scripts\activate
# Your prompt changes to show the active environment:
# (myproject-env) $
# Install packages (they go into the virtual environment, not globally)
# pip install requests flask sqlalchemy
# Check where Python and pip are located
# which python -> /path/to/myproject-env/bin/python
# which pip -> /path/to/myproject-env/bin/pip
# Deactivate when done
# deactivate
# Common convention: name the venv directory .venv
# python3 -m venv .venv
# source .venv/bin/activate
# Add .venv to .gitignore!
pip - The Package Installer
# Install a package
# pip install requests
# Install a specific version
# pip install requests==2.31.0
# Install with version constraints
# pip install "requests>=2.28,<3.0"
# Upgrade a package
# pip install --upgrade requests
# Uninstall a package
# pip uninstall requests
# List installed packages
# pip list
# Show package details
# pip show requests
# Freeze current environment to requirements.txt
# pip freeze > requirements.txt
# Install from requirements.txt
# pip install -r requirements.txt
# Search for packages (use PyPI website instead)
# https://pypi.org/search/
requirements.txt
# requirements.txt - pin exact versions for reproducibility
# requests==2.31.0
# flask==3.0.2
# sqlalchemy==2.0.25
# pydantic==2.5.3
# requirements-dev.txt - development dependencies
# -r requirements.txt # include production deps
# pytest==7.4.4
# black==24.1.1
# mypy==1.8.0
# ruff==0.1.14
# Use separate files for different environments:
# requirements.txt - production
# requirements-dev.txt - development
# requirements-test.txt - testing
Modern Tools: uv and Poetry
While venv and pip work well, modern tools like uv (from Astral, the creators of Ruff) and Poetry provide better dependency resolution, lock files, and project management.
# uv - extremely fast Python package manager
# Install uv:
# curl -LsSf https://astral.sh/uv/install.sh | sh
# Create a project
# uv init myproject
# cd myproject
# Add dependencies
# uv add requests flask
# Add dev dependencies
# uv add --dev pytest ruff
# Run your project
# uv run python main.py
# Sync dependencies (install from lock file)
# uv sync
# uv is 10-100x faster than pip!
# Poetry - dependency management and packaging
# Install: curl -sSL https://install.python-poetry.org | python3 -
# Create a new project
# poetry new myproject
# Add dependencies
# poetry add requests
# poetry add --group dev pytest
# Install all dependencies
# poetry install
# Run commands in the virtual environment
# poetry run python main.py
# poetry shell # Activate the virtual environment
pyproject.toml
The pyproject.toml file is the modern standard for Python project configuration. It replaces setup.py, setup.cfg, and requirements.txt with a single, standardized file.
# pyproject.toml
# [project]
# name = "myproject"
# version = "1.0.0"
# description = "My awesome Python project"
# requires-python = ">=3.11"
# dependencies = [
# "requests>=2.28",
# "flask>=3.0",
# "sqlalchemy>=2.0",
# ]
#
# [project.optional-dependencies]
# dev = [
# "pytest>=7.0",
# "ruff>=0.1",
# "mypy>=1.0",
# ]
#
# [tool.ruff]
# line-length = 100
# target-version = "py311"
#
# [tool.pytest.ini_options]
# testpaths = ["tests"]
Key Takeaways
- Always use virtual environments: Never install packages globally
- Pin versions: Use exact versions in production requirements
- Try uv: It is dramatically faster than pip for package management
- Use pyproject.toml: The modern standard for Python project configuration