1.3.12 · D5Python Intermediate

Question bank — Virtual environments — venv, pip, requirements.txt

1,715 words8 min readBack to topic

Before we start, the words we will lean on:

Two pictures ground the whole page — one for where things live, one for what activation changes:

Figure — Virtual environments — venv, pip, requirements.txt
Figure — Virtual environments — venv, pip, requirements.txt

True or false — justify

TF1. "A virtual environment contains a full, separately-downloaded copy of Python."
False. python -m venv copies/links the interpreter you already ran it with; nothing is downloaded. The version is fixed by which python you invoked.
TF2. "Activating a venv changes Python's behaviour permanently for your whole machine."
False. Activation only edits the PATH of the current shell session. Close the terminal and the change is gone — nothing on the machine is permanently altered.
TF3. "Two projects can safely depend on different versions of the same package if each has its own venv."
True. Each env has a private site-packages, so Django==3.2 in one and Django==4.2 in another never collide — that is the entire point of isolation.
TF4. "pip freeze lists only the packages you explicitly typed pip install for."
False. It lists every installed package including transitive dependencies (e.g. certifi pulled in by requests), each pinned with ==.
TF5. "You should commit .venv/ to git so teammates get identical packages."
False. The env is large, OS- and path-specific, and fully reproducible from requirements.txt. Commit the recipe, gitignore the cooked meal (see .gitignore best practices).
TF6. "requirements.txt stores the actual package code."
False. It is a plain-text list of names and versions — a shopping list. The code is downloaded fresh from PyPI when you run pip install -r requirements.txt.
TF7. "If pip install needs sudo, that's normal."
False. Needing sudo means you are writing into system Python, not an env — a red flag that no venv is active. Inside an active venv you never need sudo.
TF8. "Deleting the .venv/ folder loses your work."
False (usually). The env is disposable; as long as you have requirements.txt, you rebuild it in seconds. Your source code lives outside .venv/ and is untouched.
TF9. "An active venv guarantees import only ever sees the env's packages."
False. If PYTHONPATH is set or the env was made with --system-site-packages, system packages leak onto sys.path too, quietly breaking isolation.

Spot the error

SE1. A teammate runs pip install requests right after git clone, before creating any env — what's wrong?
They installed into system Python because no env was active. They should python -m venv .venv, activate, then pip install -r requirements.txt.
SE2. Someone types venv .venv and gets "command not found." What did they mean to type?
python -m venv .venv. Bare venv is not a standalone command — -m runs the venv module through a specific Python.
SE3. A script says source .venv/bin/activate but the user is on Windows and it fails. Fix?
On Windows the activator is .venv\Scripts\activate (PowerShell/cmd), not bin/activate. The bin/ path is Linux/macOS only.
SE4. requirements.txt reads flask (no version) and a build breaks a year later. What went wrong?
The unpinned line grabbed a newer, incompatible Flask. For reproducible builds you pin exact versions with == (see Semantic versioning); loose specs are for libraries, not deployments.
SE5. A user opens a second terminal, sees no (.venv) prompt, and assumes venv is broken. Real issue?
Nothing is broken — activation is per-shell. The new terminal simply hasn't run source .venv/bin/activate yet.
SE6. Someone runs pip freeze > requirements.txt without an active env and gets a giant list. Why is that bad?
They froze the global Python's packages, capturing junk unrelated to the project. freeze should be run inside the active project env only.
SE7. A .gitignore ignores venv/ but the project folder is named .venv/ — what leaks?
The .venv/ folder still gets committed, because the pattern didn't match the leading dot. The pattern must match the actual folder name (.venv/).
SE8. A fish-shell user runs source .venv/bin/activate and gets syntax errors. Fix?
bin/activate is written for bash/zsh. Fish must source .venv/bin/activate.fish; csh/tcsh users source .venv/bin/activate.csh — venv ships a script per shell dialect.
SE9. Inside an active venv, someone runs pip install --user numpy and later wonders why it appears in every project. What happened?
--user forces the install into the user-site directory (~/.local), outside the env, so it leaks across all Pythons that read user-site. Drop --user when a venv is active.

Why questions

WQ1. Why does pip install land in the env instead of system Python after activation?
Because activation put the env's bin/ first on PATH, so pip resolves to the env's private pip, which installs into the env's site-packages by default.
WQ2. Why prefer python -m venv over any venv executable on the PATH?
-m binds the env to that exact interpreter (e.g. python3.11), removing ambiguity about which Python version the env will use.
WQ3. Why is a venv described as "just PATH manipulation plus a config file," not magic?
Isolation works purely by pointing python/pip at env copies (PATH) and by pyvenv.cfg telling the env where the base standard library lives — no kernel tricks, no OS-level sandbox.
WQ4. Why do libraries publish loose (>=) requirements while apps pin exact versions?
A library wants users to receive compatible newer dependencies without conflict; a deployed app wants byte-identical, reproducible behaviour, so it pins with ==.
WQ5. Why can pip freeze in one env produce a file that fails to install cleanly in another OS?
Some pinned packages have OS-specific builds or platform-only dependencies; freezing captures your platform's exact set, which may not resolve elsewhere — a reason tools like poetry and Docker exist.
WQ6. Why does the prompt showing (.venv) matter before installing?
It is the fastest visual confirmation that installs will go into the env; without it, pip install may silently pollute global Python.
WQ7. Why does deleting and rebuilding an env not require re-downloading Python?
venv re-copies/links the already-installed base interpreter; only packages are re-fetched from PyPI. Python itself never leaves your machine.
WQ8. Why can a leftover PYTHONPATH make an "isolated" env behave non-reproducibly?
PYTHONPATH prepends extra folders to sys.path regardless of the active env, so system or stray packages get imported ahead of (or alongside) the env's — the env is no longer the sole source of truth.

Edge cases

EC1. What happens if you pip install X with no env active anywhere?
It installs into system/global Python, risking the exact dependency conflicts venv was meant to prevent — and may demand sudo.
EC2. You activate a venv, then run a script with the full path /usr/bin/python script.py. Which packages does it see?
The system Python's packages, not the env's. Activation only redirects the bare python name via PATH; an explicit path bypasses that entirely.
EC3. An empty freshly-created venv — what's in its site-packages?
Essentially nothing beyond pip and its bootstrap machinery; it starts empty and waits for your installs, which is why isolation is clean from line one.
EC4. Two projects share the same folder and thus the same .venv/ — problem?
They now share one site-packages, reintroducing version collisions. One folder should map to one env; merging defeats isolation.
EC5. You forget to deactivate and create a nested venv from inside an active one — what version does it use?
It builds from the currently active env's interpreter, which may not be the base Python you intended. Deactivate first, or invoke a specific python3.x -m venv.
EC6. requirements.txt exists but is empty — what does pip install -r requirements.txt do?
Nothing installs; it succeeds silently with an empty package set. The env stays bare, which can masquerade as "it worked" while the app then fails on missing imports.
EC7. An env created with python -m venv --system-site-packages .venv — how is its isolation different?
It intentionally lets system-installed packages show through into the env, so an import may succeed on a package you never installed locally — handy sometimes, but it breaks strict reproducibility.
EC8. A user has export PYTHONPATH=~/mylibs in their shell rc, then activates a venv — what still leaks in?
Everything under ~/mylibs remains on sys.path for the active env too, so those modules import regardless of the venv. Unset PYTHONPATH for truly clean isolation.
EC9. pip install --user is run outside any venv — where does it go and why does it confuse people later?
Into the user-site directory (~/.local/lib/...), which many system Pythons read by default, so it appears "installed everywhere" and can shadow or conflict with env packages unexpectedly.

Recall One-line self-test before you close this page

If you can explain why activation is per-shell, why .venv/ is gitignored, why python -m venv fixes the version, and why PYTHONPATH/--user/--system-site-packages can each punch a hole in isolation — in your own words — you've beaten the traps that catch almost everyone.