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 whatpyproject.tomlis. - 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.
| System | Activate |
|---|---|
| macOS / Linux (bash, zsh) | source .venv/bin/activate |
| Windows (cmd.exe) | .venv\Scripts\activate.bat |
| Windows (PowerShell) | .venv\Scripts\Activate.ps1 |
| fish shell | source .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.
| Command | Does |
|---|---|
python -m pip install requests | install the latest version |
python -m pip install requests==2.32.3 | install an exact version |
python -m pip install "requests>=2.30,<3" | install within a version range |
python -m pip install --upgrade requests | upgrade to the newest version |
python -m pip uninstall requests | remove it (dependencies stay) |
python -m pip show requests | version, location, and dependencies |
python -m pip list | everything installed in this environment |
python -m pip freeze > requirements.txt | write the exact versions to a file |
python -m pip install -r requirements.txt | install 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:
| Tool | What it does |
|---|---|
| uv | a very fast, all-in-one replacement for pip, venv and Python version management (uv venv, uv pip install, uv run) |
pytest | the most popular test framework |
ruff | a linter and formatter that enforces PEP 8 and finds bugs |
mypy / pyright | static type checkers that read your annotations (Chapter 5) |
pipx | installs command-line applications (like ruff) into their own isolated venvs |
What now?
You've covered the whole official tutorial. Where to go from here:
- The Python Standard Library: skim the table of contents so you know what exists.
- The Language Reference: the precise rules, for when you need them.
- The Python HOWTOs: in-depth guides to logging, sorting, regex, descriptors and more.
- The Python Packaging User Guide: how to publish your own packages.
- The Python FAQ and discuss.python.org for questions.
- Then pick a domain: web (FastAPI, Django), data (pandas, Polars), automation (pathlib, subprocess, httpx), or testing (pytest).
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/pythondirectly. - Install with
python -m pip install pkg. Pin versions with==,>=or~=. - Use
pip freeze > requirements.txtandpip install -r requirements.txtfor reproducible setups. - Never
sudo pip. "externally-managed-environment" means "use a venv". Don't commit.venv/. pyproject.tomldescribes modern projects.uv,pytest,ruffandmypyare 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.