Exercises — Virtual environments — venv, pip, requirements.txt
The five levels, in plain words:
| Level | Name | What it asks of you |
|---|---|---|
| L1 | Recognition | Recall a fact or spot the right command. |
| L2 | Application | Run the standard workflow correctly. |
| L3 | Analysis | Figure out why something broke or differs. |
| L4 | Synthesis | Combine ideas into a plan or a file. |
| L5 | Mastery | Reason about edge cases most people never meet. |
L1 — Recognition
Exercise 1.1
Which single command creates a virtual environment named .venv in the current folder — and why is it written this exact way?
Recall Solution
python -m venv .venvpython= the interpreter you want the env built from.-m venv= "run the built-in module calledvenv", not a loose script on your PATH..venv= the name (a folder) the env will live in. The leading dot makes it a hidden, conventional name.
Writing -m matters: it guarantees the env is built with that specific Python. Compare pip and PyPI where python -m pip is preferred for the same reason.
Exercise 1.2
Match each term to its one-line job: venv, pip, requirements.txt, activate.
Recall Solution
venv::: the module that creates the isolated folder.pip::: the tool that installs packages into the active env (from PyPI).requirements.txt::: the plain-text list of versions to rebuild the env anywhere.activate::: edits the shellPATHsopython/pippoint at this env.
Exercise 1.3
True or false: python -m venv .venv downloads a fresh copy of Python from the internet.
Recall Solution
False. It copies or links the Python you already invoked. No download happens; the env's Python version is fixed by which python you typed. See the figure below.

L2 — Application
Exercise 2.1
Write the full command sequence to start a new project scraper, isolate it, install requests and beautifulsoup4, and save the exact versions to a file.
Recall Solution
mkdir scraper && cd scraper
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install requests beautifulsoup4
pip freeze > requirements.txtThe order is the C-A-I-F rhythm: Create → Activate → Install → Freeze. Freezing after installing captures both packages and their transitive dependencies (things they pull in automatically).
Exercise 2.2
A teammate hands you a repo containing app.py and requirements.txt but no .venv/ folder. Give the commands that reproduce their environment.
Recall Solution
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtThe .venv/ was intentionally left out (it's gitignored) because it is rebuildable. -r means "read this file line by line and install each pinned version". This is the foundation of reproducible builds.
Exercise 2.3
You run pip install flask. Before pressing enter, what one thing should your terminal prompt show, and what command confirms it?
Recall Solution
The prompt should show (.venv) at its start, meaning the env is active. Confirm the redirect with:
which pip # Windows: where pipIt should print a path inside .venv/bin/pip, not a system path. If it prints a system path, you'd pollute global Python — see the figure.

L3 — Analysis
Exercise 3.1
You open a brand-new terminal, cd into your project, and run pip install rich. It installs to your system Python instead of the venv. Explain exactly why.
Recall Solution
Activation edits PATH only for the shell session it ran in. A new terminal is a fresh session with the original PATH, so pip resolves to system pip again. Nothing about the .venv/ folder changed — the link between your shell and the env simply was never made in this window.
Fix: source .venv/bin/activate in every new terminal. Editors like VS Code auto-activate when they detect .venv.
Exercise 3.2
Two projects on one laptop:
- Project A needs
Django==3.2 - Project B needs
Django==4.2
Explain, in terms of site-packages, why a single global Python cannot satisfy both, but two venvs can.
Recall Solution
A Python installation stores installed packages in one site-packages directory. A directory can hold only one version of Django at a time — installing 4.2 overwrites 3.2. That is dependency hell.
Each venv owns a separate site-packages. Project A's env holds 3.2, Project B's env holds 4.2, and because sys.path inside each env points at its own folder, imports never cross over. The figure shows the two separate boxes.

Exercise 3.3
pip freeze on a project that installed only flask outputs more than one line:
flask==3.0.3
Werkzeug==3.0.3
Jinja2==3.1.4
...Why are there extra packages you never installed?
Recall Solution
Those extras are transitive dependencies: packages that flask itself requires to run. pip resolves and installs them automatically. pip freeze reports everything present in site-packages, direct and transitive alike — which is exactly what you want for reproducibility, because the app needs all of them.
L4 — Synthesis
Exercise 4.1
You are shipping a production web service and publishing a reusable library. Write the appropriate requirements line for requests in each case and justify the difference.
Recall Solution
- Production service:
requests==2.32.3— a hard pin. You want the exact bytes that you tested, every deploy. Reproducibility beats freshness. - Library:
requests>=2.31— a floor, not a pin. Your library will be installed alongside other packages in someone else's env; over-pinning would cause conflicts. You state the minimum you need and let their resolver pick a compatible newer version.
The == vs >= choice follows semantic versioning: the pin locks major.minor.patch; the floor trusts backward-compatible updates.
Exercise 4.2
Write a minimal .gitignore for a Python project using a venv, and explain each line.
Recall Solution
.venv/ # the environment — rebuildable, huge, OS-specific
__pycache__/ # compiled bytecode caches — regenerated automatically
*.pyc # individual compiled filesThe principle from .gitignore best practices: version-control the recipe (requirements.txt, source code), never the cooked meal (.venv/, caches). If a file can be regenerated from what you do commit, it does not belong in git.
Exercise 4.3
Design a two-file setup that lets contributors install either just what's needed to run the app, or run-plus-testing tools. Sketch the files.
Recall Solution
# requirements.txt (runtime only)
flask==3.0.3
requests==2.32.3
# requirements-dev.txt (adds test/lint tooling)
-r requirements.txt # include everything above
pytest==8.3.2
ruff==0.6.1A contributor running the app: pip install -r requirements.txt.
A contributor developing: pip install -r requirements-dev.txt (the -r requirements.txt line pulls the runtime set in first). This layered pattern is what heavier tools like poetry and pipenv formalise into "dependency groups".
L5 — Mastery
Exercise 5.1
A colleague's requirements.txt says numpy==1.26.4. It installs fine on their Mac but fails to build on your Linux CI machine with a compiler error. The version is identical. What subtle thing does requirements.txt not capture, and what tool tier fixes it?
Recall Solution
requirements.txt pins package versions, but not:
- the Python version (3.11 vs 3.12 changes which prebuilt wheels exist),
- the operating system / CPU architecture (a Mac wheel ≠ a Linux wheel),
- system-level C libraries the build needs.
So "same version" is not "same environment". The fix is a stronger reproducibility tier: a lockfile that records the exact wheel hashes (via poetry/pipenv) and, for full OS reproducibility, a container (see Reproducible builds and Docker). The container freezes the OS, the Python, and the packages together.
Exercise 5.2
Explain why python -m venv .venv can safely run without internet, yet pip install -r requirements.txt usually cannot. Then name the flag that lets pip also work offline.
Recall Solution
venvonly copies/links Python that is already on your disk and writes a tinypyvenv.cfg. Nothing is fetched — hence offline-safe.pip installmust download packages from PyPI (a remote index), so it normally needs a network. See pip and PyPI.- Offline install is possible if the wheels are pre-downloaded locally:
pip install --no-index --find-links=./wheels -r requirements.txt.--no-indexsays "don't talk to PyPI",--find-linkspoints at a local wheel folder.
Exercise 5.3
You activate .venv, then inside it run python -m venv .venv2. Which Python does .venv2 end up using — the base system Python or the venv's Python — and why does the answer follow directly from what -m means?
Recall Solution
.venv2 uses the active env's Python. Here's the chain of reasoning:
-m venvruns the module with whateverpythonresolves to right now.- Inside an active env,
pythonresolves (via the editedPATH) to.venv/bin/python. - So
venvbuilds.venv2from.venv's interpreter.
However, pyvenv.cfg in .venv2 will point back to the base Python's standard library (venv chains resolve to the ultimate base install for the stdlib), so .venv2 still finds the standard library correctly. The takeaway: the interpreter that runs -m venv decides the new env's Python — nesting doesn't create a new interpreter, it just relinks.
Recall One-line summary of the whole page
C-A-I-F-D is the muscle memory; pin apps and float libraries; gitignore the env and commit the recipe; and remember that requirements.txt reproduces versions, while a container reproduces the whole world.