Chapter 15 · Standard Library & Ecosystem

Virtual Environments & pip

Give every project its own isolated set of packages, install anything from PyPI with pip, record your dependencies, and see where to go next.

You will learn

  • Why projects need isolated environments.
  • How to create, activate and deactivate a virtual environment with venv.
  • How to install, upgrade, inspect and remove packages with pip.
  • How to record dependencies in requirements.txt, and what pyproject.toml is.
  • The common tools and next steps after the tutorial.

Why virtual environments?

Packages from the internet are installed into a folder called site-packages. If every project shared one Python installation, conflicts would follow quickly. Project A needs version 1 of a library, project B needs version 2, and only one can be installed at a time.

A virtual environment (a "venv") is a self-contained folder with its own Python executable and its own site-packages. Each project gets one, so the projects can never break each other.

Analogy: each project gets its own toolbox, so lending a hammer to one project never takes it away from another.

☕ Java: this is like Maven or Gradle giving each project its own dependency set, except that in Python you activate the environment in your shell. 🟨 JS: a venv is like a project's node_modules folder, and requirements.txt (or pyproject.toml) plays the role of package.json.

Creating a virtual environment

Run the venv module with the folder name you want. .venv is the usual convention: the leading dot hides it, and many tools look for it automatically.

(On Windows the folder is called Scripts instead of bin.) Inside it are a Python executable, pip, and an empty site-packages.

Activating it

Activating puts the venv's bin folder at the front of your PATH (Appendix B), so python and pip now mean the venv's copies. Your prompt usually shows the venv's name.

SystemActivate
macOS / Linux (bash, zsh)source .venv/bin/activate
Windows (cmd.exe).venv\Scripts\activate.bat
Windows (PowerShell).venv\Scripts\Activate.ps1
fish shellsource .venv/bin/activate.fish

You can also skip activation and call the venv's Python directly. That is handy in scripts and in CI:

💡 Tip: add .venv/ to .gitignore. Never commit a venv: it is large, and tied to one machine and one Python version. Commit the list of dependencies instead, and recreate the venv from it.

Managing packages with pip

pip installs packages from the Python Package Index (PyPI), the public repository of more than half a million packages. Run it as python -m pip, so that you always use the pip that belongs to the Python you mean.

CommandDoes
python -m pip install requestsinstall the latest version
python -m pip install requests==2.32.3install an exact version
python -m pip install "requests>=2.30,<3"install within a version range
python -m pip install --upgrade requestsupgrade to the newest version
python -m pip uninstall requestsremove it (dependencies stay)
python -m pip show requestsversion, location, and dependencies
python -m pip listeverything installed in this environment
python -m pip freeze > requirements.txtwrite the exact versions to a file
python -m pip install -r requirements.txtinstall everything listed in the file

requirements.txt

A plain text file with one requirement per line. Commit it, so that teammates and servers can rebuild the same environment:

⚠️ Pitfall: "externally-managed-environment". If you run pip install outside a venv on a modern Linux or Homebrew Python, pip refuses with error: externally-managed-environment (PEP 668). That is deliberate: it protects the system's own Python. The fix is to use a venv. Never "fix" it with sudo pip.

❌ Global installs

✅ Per-project venv

pyproject.toml and modern tools

Real projects describe themselves in pyproject.toml: the name, the version, the dependencies, and tool settings. Packaging tools read it to build and install your project. You'll meet it in almost every open-source repository.

Popular tools that work with all this:

ToolWhat it does
uva very fast, all-in-one replacement for pip, venv and Python version management (uv venv, uv pip install, uv run)
pytestthe most popular test framework
ruffa linter and formatter that enforces PEP 8 and finds bugs
mypy / pyrightstatic type checkers that read your annotations (Chapter 5)
pipxinstalls command-line applications (like ruff) into their own isolated venvs

What now?

You've covered the whole official tutorial. Where to go from here:

More examples

1. A fresh project from zero

2. Is this script running inside a venv?

3. Listing installed packages from Python

4. Recreating an environment exactly

5. The same with uv

Recap

  • One venv per project: python3 -m venv .venv. Activate it, or call .venv/bin/python directly.
  • Install with python -m pip install pkg. Pin versions with ==, >= or ~=.
  • Use pip freeze > requirements.txt and pip install -r requirements.txt for reproducible setups.
  • Never sudo pip. "externally-managed-environment" means "use a venv". Don't commit .venv/.
  • pyproject.toml describes modern projects. uv, pytest, ruff and mypy are the common companions.

Exercises

Exercise 1 🟢 easy build it · playground

Create a venv inside playground/, activate it, and confirm with which python (or where python on Windows) that it is active.

Show solution

The prompt prefix and the path both show that python now points into .venv.

Exercise 2 🟢 easy explain why

Why should you write python -m pip install x instead of just pip install x?

Show solution

If several Pythons are installed, pip might belong to a different one than python, so the package lands in the wrong place. python -m pip always uses the pip of the Python you named.

Exercise 3 🟢 easy predict the output

Which versions does pytest~=8.3 allow: 8.2, 8.3, 8.9, 9.0?

Show solution

8.3 and 8.9. ~=8.3 means >=8.3, <9: compatible releases within major version 8, starting from 8.3.

Exercise 4 🟢 easy fix the bug

A teammate's repository has a 300 MB .venv folder committed, and it doesn't work on your machine. What should the repository contain instead?

Show solution

A requirements.txt (or pyproject.toml) listing the dependencies, plus .venv/ in .gitignore. Each person creates their own venv and installs from the list. A venv contains absolute paths and platform-specific binaries, so it can't be copied between machines.

Exercise 5 🟡 medium build it

Write a script check_env.py that prints whether it is running in a venv, the Python version, and the path of the executable, and that exits with code 1 if it is not in a venv.

Show solution

Inside a venv, sys.prefix points to the venv while sys.base_prefix still points to the original installation.

Exercise 6 🟡 medium explain why

You run pip install rich on Ubuntu outside any venv, and you get error: externally-managed-environment. What does it mean, and what are the two right fixes?

Show solution

The OS package manager owns the system Python, and installing into it with pip could break system tools (PEP 668). Fix 1: create a venv and install there. Fix 2: for a command-line app, use pipx install, which manages a venv for it. Don't use --break-system-packages or sudo.

Exercise 7 🟡 medium build it · playground

Inside the venv from Exercise 1, install pytest, write playground/test_money.py with two plain-assert tests for format_money (from money.py), and run python -m pytest -q.

Show solution

pytest finds files named test_*.py and functions named test_*. Plain assert statements are enough, and pytest shows a detailed message when one fails.

Exercise 8 🟡 medium refactor it

This requirements.txt is fragile. Improve it so that it is reproducible but still accepts bug-fix releases: requests, rich, pytest.

Show solution

Unpinned names install whatever is newest on the day you install, which could include breaking major versions. ~= allows new minor and patch releases (2.33 is fine) but blocks the next major version (3.0). For exact reproducibility, deploy from a pip freeze lock file.

Exercise 9 🔴 hard explain why

After source .venv/bin/activate, how does your shell "know" to use the venv's Python? What exactly did activation change?

Show solution

The activate script puts .venv/bin at the front of the PATH environment variable, sets VIRTUAL_ENV, changes the prompt, and defines a deactivate function. When you type python, the shell searches PATH left to right and finds .venv/bin/python first. That Python reads .venv/pyvenv.cfg, sets sys.prefix to the venv, and uses its site-packages. Nothing else changes, and that is why calling .venv/bin/python directly works without activating.

Exercise 10 🔴 hard mini-project · playground

Turn playground/ into a proper project: add a pyproject.toml for the tracker package (Chapter 8), with requires-python = ">=3.15" and a [project.scripts] entry called expenses. Then run python -m pip install -e . in the venv, so that typing expenses in the terminal runs your package.

Show solution

This needs tracker/__main__.py to define a main() function (Chapter 16 does exactly that). -e is an "editable" install: the command uses your source files directly, so edits take effect without reinstalling.

Official docs