Worked examples — Virtual environments — venv, pip, requirements.txt
This page walks through every kind of situation virtual environments throw at you: fresh builds, broken activations, version pins, the weird edge cases (empty files, wrong Python, missing env), and an exam twist. First we map the whole territory, then we hit every cell with a fully worked example.
If any word here feels new — venv, pip, PATH, site-packages — it is fully built in the parent note. This page assumes you have met those and now wants you to become unshakeable across all cases.
The scenario matrix
Think of every venv problem as landing in one of these cells. The goal of the worked examples below is that after them, you never meet a cell you haven't already seen solved.
| Cell | Case class | The question it tests | Example |
|---|---|---|---|
| A | Fresh build (the happy path) | Can you create → activate → install → freeze? | Ex 1 |
| B | Reproduce from a file | Can you rebuild someone else's exact env? | Ex 2 |
| C | Version conflict (two projects) | Two Djangos at once — the core "why" | Ex 3 |
| D | Wrong interpreter | You built the env with the wrong Python | Ex 4 |
| E | Degenerate: empty / missing file | requirements.txt empty or absent |
Ex 5 |
| F | Pinned vs loose (== vs >=) |
Reading a requirements file & predicting installs | Ex 6 |
| G | Not activated (the silent trap) | Install "worked" but went to the wrong place | Ex 7 |
| H | Word problem (real world) | Deploy identical env on a server | Ex 8 |
| I | Exam twist | Trace sys.path — why isolation happens |
Ex 9 |

Cell A — Fresh build (happy path)
Forecast: Guess before reading — after pip install requests, where does the requests folder physically land? System Python, or somewhere inside your project?
Steps:
-
mkdir weather && cd weatherWhy this step? One folder = one project = one env. This keeps each project's toy box separate (parent-note picture). -
python -m venv .venvWhy this step? The-m venvruns the built-in venv module to build an isolated interpreter folder..venvis the conventional hidden name so it doesn't clutter listings. -
source .venv/bin/activateWhy this step? Activation edits the shell'sPATHsopythonandpipnow resolve to the copies inside.venv. Your prompt changes to(.venv). -
pip install requestsWhy this step? Because the activepipis the env'spip,requestslands inweather/.venv/lib/python3.11/site-packages/, not system Python. -
pip freeze > requirements.txtWhy this step?freezeprints every installed package with its exact version;>writes that to a file — the reproducible recipe.
Verify: Run which python → it must print a path ending in weather/.venv/bin/python. And cat requirements.txt shows something like:
certifi==2024.7.4
charset-normalizer==3.3.2
idna==3.7
requests==2.32.3
urllib3==2.2.2Notice: you asked for 1 package (requests) but got 5 lines. The extra 4 are transitive dependencies — packages requests itself needs. Count check: 1 requested + 4 transitive = 5 lines.
Cell B — Reproduce from a file
Forecast: Will git clone give you the packages, or only the list of packages?
Steps:
-
git clone https://example/cool-app && cd cool-appWhy this step? You receive the code and the recipe file. The env is gitignored, so you get the shopping list, not the groceries. -
python -m venv .venvWhy this step? The env is disposable and local — you always rebuild, never download it. -
source .venv/bin/activateWhy this step? Pointpipat this fresh empty env before installing. -
pip install -r requirements.txtWhy this step?-rmeans "read this file line by line and install each pinned version". Same versions ⇒ same behaviour as the author.
Verify: pip freeze on your machine should print a file identical to the teammate's requirements.txt (same lines, same order after sorting). If they match, reproduction succeeded.
Cell C — Version conflict (the whole reason venv exists)
Forecast: With global install only, how many Django versions can coexist? With venvs, how many?
Steps:
-
Build A's env and pin its Django:
cd projectA && python -m venv .venv && source .venv/bin/activate pip install Django==3.2 deactivateWhy this step? A's
site-packagesnow holds only 3.2, sealed off. -
Build B's env separately:
cd ../projectB && python -m venv .venv && source .venv/bin/activate pip install Django==4.2Why this step? B's
site-packagesholds 4.2. Different folder ⇒ no collision. -
Check versions per env:
- In A:
python -c "import django; print(django.VERSION[:2])"→(3, 2) - In B:
python -c "import django; print(django.VERSION[:2])"→(4, 2)
- In A:
Verify: Globally you could hold 1 version at a time (installing 4.2 overwrites 3.2). With venvs you hold 2 simultaneously — one per env. That difference (1 → 2) is exactly the dependency-hell problem being solved.

Cell D — Wrong interpreter
Forecast: Does python -m venv download a Python version, or copy the one you invoked?
Steps:
-
Diagnose:
.venv/bin/python --version→Python 3.9.6. Why this step? The env's Python version is frozen to whateverpythonpointed to at creation time.venvcopies/links; it never downloads a new interpreter. -
Delete the wrong env:
rm -rf .venvWhy this step? Envs are disposable. You don't "upgrade" an env's Python — you rebuild. -
Recreate with the explicit version:
python3.11 -m venv .venvWhy this step? Namingpython3.11fixes the interpreter. This is the whole reason we preferpython -m venvover a barevenvcommand — you control which Python builds it. -
source .venv/bin/activate && python --version→Python 3.11.x.
Verify: After the fix, .venv/bin/python --version reports 3.11. If your machine has both, the version number must match the python3.11 you invoked, never a downloaded one.
Cell E — Degenerate input: empty / missing file
Forecast: Empty file → error, or silently install nothing? Missing file → error, or silently install nothing?
Steps:
-
Case (i), empty file:
pip install -r requirements.txtWhy this step? pip reads zero lines, so it installs zero packages and exits successfully (exit code0). No error — an empty recipe is a valid recipe for "nothing". -
Case (ii), file absent:
pip install -r requirements.txtWhy this step? pip cannot open a file that isn't there, so it errors withCould not open requirements fileand a non-zero exit code. -
Distinguish them: an env built from case (i) has only the base packages (
pip,setuptools); case (ii) never even starts installing.
Verify: Number of packages installed in case (i) = 0. Exit code in case (i) = 0; exit code in case (ii) ≠ 0 (it's 1). These two degenerate inputs give different outcomes — empty ≠ missing.
Cell F — Pinned vs loose (== vs >=)
Forecast: Which of the two lines is guaranteed to give the same version next month?
Steps:
-
Read
flask==3.0.3. Why this step?==is an exact pin. pip must install precisely3.0.3, today and forever. Result:flask 3.0.3. -
Read
requests>=2.28. Why this step?>=means "at least this version", so pip installs the newest version that is ≥ 2.28. Today that's2.32.3. Tomorrow, if2.33.0releases, this line would install2.33.0instead. -
Classify: pinned line = reproducible; loose line = "give me compatible latest".
Verify: Today's result → flask 3.0.3, requests 2.32.3. Stability count: exactly 1 of the 2 lines (flask==3.0.3) is guaranteed identical next month; the >= line may drift. See Semantic versioning for how >=, ~=, and ^ bounds are chosen.
Cell G — The silent trap (not activated)
Forecast: If the prompt did not show (.venv) when you installed, whose site-packages got rich?
Steps:
-
Check activation: prompt shows plain
$, not(.venv) $. Why this step? No(.venv)means the shellPATHwas never redirected —pipis the system pip. -
Confirm:
which pip→/usr/bin/pip(system), not.../project/.venv/bin/pip. Why this step? This proves the install went into system Python'ssite-packages, outside your env. -
Fix: activate, then reinstall.
source .venv/bin/activate pip install richWhy this step? Now
pipis the env's pip, sorichlands in the env, where your project'spythonlooks. -
Retry:
python -c "import rich; print('ok')"→ok.
Verify: Before fix, import rich fails inside the env because the env's site-packages had it in 0 places. After activating and installing, it appears in exactly 1 place (the env), and the import succeeds. Always confirm (.venv) and which pip before installing.
Cell H — Word problem (real-world deploy)
Forecast: Should you copy your Mac's .venv/ folder to the server, or do something else?
Steps:
-
On your Mac:
pip freeze > requirements.txtand commit it. Why this step? Freeze captures the exact version of every package (direct + transitive). This is the portable recipe. -
On the server:
python3.11 -m venv .venv && source .venv/bin/activateWhy this step? Build a fresh Linux-native env. Never copy the Mac.venv/— its binaries are compiled for macOS and won't run on Linux. -
pip install -r requirements.txtWhy this step? Installs the identical pinned versions, but compiled for the server's OS. Same versions + native binaries = same behaviour, no crash.
Verify: After step 3, run pip freeze on the server and diff against the committed requirements.txt — they must be identical. Copying the Mac .venv/ would have failed because binaries are OS-specific; rebuilding from the recipe works. For fully deterministic deploys see Reproducible builds and Docker and .gitignore best practices (why .venv/ stays out of git).
Cell I — Exam twist: trace why isolation happens
Forecast: What single list does Python search when you write import X, and what changes it?
Steps:
-
Define the tool.
sys.pathis the ordered list of folders Python searches, top to bottom, to satisfy animport. Why this tool? Because imports are the only way packages become usable — so isolation must show up here or nowhere. -
Before activation,
python -c "import sys; print(sys.path)"shows folders ending in the systemsite-packages, e.g./usr/lib/python3.11/site-packages. Why this step? System Python builds its path from the system install location. -
After
source .venv/bin/activate, the same command shows the env'ssite-packagesfirst, e.g./proj/.venv/lib/python3.11/site-packages, and the system one is dropped (thanks topyvenv.cfgwithinclude-system-site-packages = false). Why this step? Activation puts the env'spythonfirst onPATH; that interpreter readspyvenv.cfgand builds a path pointing at the env's packages, not the system's. -
Conclusion: because
importonly ever searches the activesys.path, and the env's path excludes systemsite-packages, installs into the env are invisible to system Python and vice versa.
Verify: The distinguishing fact: after activation, the number of system site-packages entries in sys.path is 0 (when include-system-site-packages = false). That zero is the mechanical reason isolation holds. Details of path construction: Python import system and sys.path. Contrast with heavier tools in Dependency management — poetry, pipenv and the underlying installer in pip and PyPI.

Recall Did every cell get covered?
Fresh build (A), reproduce (B), version conflict (C), wrong Python (D), empty vs missing file (E), pinned vs loose (F), silent not-activated trap (G), real-world deploy (H), exam sys.path trace (I). Nine cells, nine examples.
Which cell tests the degenerate empty-input case?
requirements.txt (empty installs 0 packages, exit 0; missing errors, exit non-zero).Why can't you copy a .venv/ from Mac to a Linux server?
requirements.txt on the server instead.After activation with include-system-site-packages = false, how many system site-packages entries are in sys.path?
In requests>=2.28, what version installs if 2.32.3 is newest?
>= installs the newest version that satisfies the bound.