Virtual Environments and pip
Isolating dependencies with venv, installing packages with pip, and locking versions
A Python install ships with a small standard library. Anything beyond that — NumPy, requests, Flask, pandas — comes from PyPI, the Python Package Index. You install packages with pip and you keep projects from stepping on each other's toes with virtual environments.
Never pip install into system Python
Without a virtual environment, `pip install` modifies your global Python installation. Two projects that need different versions of the same package cannot coexist. Worse, you risk breaking system tools that depend on specific versions. Every Python developer uses virtual environments. No exceptions.
This page is reference material; the commands below are intended to be run in your terminal on your local machine, not in the interactive code blocks.
Why virtual environments?
Imagine you have two projects:
- Project A needs
requests==2.25.1(an old version for compatibility with legacy code). - Project B needs
requests>=2.31.0(the latest, for new features).
Without isolation, both projects share the same site-packages directory. Installing one version breaks the other. A virtual environment solves this by creating a self-contained Python installation for each project, with its own site-packages.
Each virtual environment has its own Python interpreter copy (symlinked on Unix, copied on Windows) and its own site-packages directory. Activating a venv puts it first on your PATH, so python and pip point to that isolated environment.
Real-world necessity
Every Python team uses virtual environments. Whether you're building a web API, a data pipeline, a machine learning model, or a CLI tool, you will create a venv (or use a modern tool like `uv` or `poetry` that manages venvs for you). Dependency isolation is non-negotiable in professional Python work.
Creating a virtual environment with venv
venv ships with Python 3. From any project directory:
# Create the environment in a folder called .venv
python3 -m venv .venvThis creates a .venv/ directory containing:
- A copy (or symlink) of the Python interpreter.
- A
site-packages/folder for installed packages. - Activation scripts to modify your shell's
PATH.
Why .venv?
The name `.venv` is a convention. You can name it `venv`, `env`, or `my-special-env`, but `.venv` is standard because (1) the leading dot hides it in Unix file listings, and (2) most tools auto-detect it. Stick with `.venv` unless you have a reason not to.
Activating the environment
Activation modifies your shell's PATH so python and pip point to the virtual environment:
# macOS / Linux
source .venv/bin/activate
# Windows (Command Prompt)
.venv\\Scripts\\activate.bat
# Windows (PowerShell)
.venv\\Scripts\\Activate.ps1Your prompt will change to show the active environment:
(.venv) user@host:~/project$Now python --version and which python (or where python on Windows) point inside .venv/.
Deactivating
When you're done:
deactivateYour prompt returns to normal, and python points back to the system installation.
Installing packages with pip
Inside the activated environment:
pip install requests
pip install "numpy>=2.0,<3"
pip install -U requests # upgrade to latest
pip uninstall requests
pip list # what is installed
pip show requests # details for one packagePackages are installed into .venv/site-packages/, isolated from the system and other venvs.
# Example: after 'pip install requests' in your venv
import requests
response = requests.get("https://api.github.com")
print(response.status_code)
print(response.json()["current_user_url"])Pinning versions with requirements.txt
requirements.txt records what your project needs. Generate it from the active environment:
pip freeze > requirements.txtThis writes every installed package with an exact version:
certifi==2024.8.30
charset-normalizer==3.4.0
idna==3.10
requests==2.32.3
urllib3==2.2.3To reproduce the environment later (on another machine, in CI, on a server):
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtrequirements.in vs requirements.txt
`pip freeze` lists every dependency, including transitive ones (dependencies of dependencies). Many teams maintain a shorter `requirements.in` with only top-level packages:
``` requests>=2.31 flask>=3.0 ```
Then use `pip-tools` (`pip-compile requirements.in`) or `uv pip compile requirements.in` to generate a fully locked `requirements.txt`. This keeps your source file readable while ensuring reproducibility.
.gitignore your virtual environment
Never commit a virtual environment to version control. Virtual environments are not portable across machines or operating systems. Commit requirements.txt or pyproject.toml instead.
Add to .gitignore:
.venv/
venv/
env/
*.pyc
__pycache__/Modern alternatives: uv, poetry, hatch, pdm
The Python packaging ecosystem is rapidly improving. As of 2026, the most popular modern tools are:
uv (recommended)
uv is a Rust-based tool that replaces pip, pip-tools, and venv. It's 10–100× faster and handles Python version management too.
# Install uv (from their docs)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create a venv with uv
uv venv
# Install packages
uv pip install requests
# Compile a lockfile
uv pip compile requirements.in -o requirements.txt
# Sync the venv to the lockfile
uv pip sync requirements.txtuv is fast enough to make dependency resolution feel instant, even on large projects.
poetry
poetry combines dependency management, virtual environments, and packaging into one tool driven by pyproject.toml:
poetry init
poetry add requests
poetry install
poetry run python script.pypoetry auto-manages the venv and generates poetry.lock for reproducibility.
hatch and pdm
hatch and pdm are similar pyproject.toml-based workflows with strong plugin ecosystems.
All of these tools speak pyproject.toml, the standard project metadata file defined in PEP 621.
pyproject.toml at a glance
pyproject.toml replaces the older setup.py for the vast majority of projects. It defines project metadata, dependencies, and build configuration:
[project]
name = "my-app"
version = "0.1.0"
description = "A small Python app"
requires-python = ">=3.11"
dependencies = [
"requests>=2.31",
"rich>=13.0",
]
[project.optional-dependencies]
dev = ["pytest", "ruff", "mypy"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"Most modern tools can read this file and install the listed dependencies.
Cheat sheet
| Task | Command |
|---|---|
| Create venv | python3 -m venv .venv |
| Activate (macOS/Linux) | source .venv/bin/activate |
| Activate (Windows PS) | .venv\\Scripts\\Activate.ps1 |
| Deactivate | deactivate |
| Install one package | pip install requests |
| Install from file | pip install -r requirements.txt |
| Freeze versions | pip freeze > requirements.txt |
| Upgrade pip itself | python -m pip install -U pip |
| List installed | pip list |
| Show one package | pip show requests |
| Uninstall | pip uninstall requests |
Avoid --user
You may see advice to run `pip install --user`. Skip it. The `--user` flag installs packages into your home directory's `site-packages`, which is still global (shared across all your projects). Always work inside a virtual environment instead — it's cleaner, more portable, and more predictable.
Directory structure example
After creating and activating a venv, your project might look like:
my-project/
├── .venv/ # virtual environment (in .gitignore)
│ ├── bin/ # activation scripts, python, pip
│ ├── lib/
│ │ └── python3.12/
│ │ └── site-packages/ # installed packages
│ └── pyvenv.cfg
├── src/
│ └── main.py
├── tests/
│ └── test_main.py
├── requirements.txt # or requirements.in + requirements.txt
├── pyproject.toml # modern projects
├── README.md
└── .gitignore # includes .venv/The .venv/ directory is never committed. Your teammates and CI systems recreate it from requirements.txt or pyproject.toml.
Multiple-choice questions
Why should you use a virtual environment for each Python project?
It makes import statements run faster.
It isolates each project's dependencies so they do not conflict with each other or with the system Python.
It is the only way to use pip.
It compiles your code to a single executable.
What does activating a virtual environment do?
It modifies your shell's PATH so python and pip point to the venv's binaries.
It permanently changes the system Python installation.
It installs all packages listed in requirements.txt.
It compiles Python to bytecode for faster execution.
What should you commit to version control?
The .venv/ directory, so everyone has the same packages.
requirements.txt or pyproject.toml, so teammates can recreate the environment.
Nothing — each developer should figure out dependencies on their own.
Only the Python interpreter binary.
Which tool is a modern, fast alternative to pip and venv?
npm
uv
conda
virtualenv
You now have everything you need to start writing real Python projects. The final page surveys what to learn next.