I standardized Python project setup on isolated virtual environments with pinned dependencies and a clean package layout. Per-project venvs eliminated dependency conflicts and made every script reproducible from a single requirements file.

Objective & Context

Global package installs cause version conflicts across projects. This lab establishes venv isolation and pinned requirements as the baseline for every later Python lab, ensuring deterministic runs on any machine.

Learning Objectives

  • Create and activate an isolated venv per project so dependencies never leak between projects.
  • Install packages with pip and pin the full dependency tree with pip freeze.
  • Verify which interpreter and package set a shell is using with sys.prefix and pip list.
  • Recreate an environment from requirements.txt on a fresh directory.
  • Adopt a standard project layout (src/, requirements, .venv) reused across all later labs.

Environment & Prerequisites

Prerequisites

  • Python 3.11 or newer installed; check with python3 --version
  • venv module available (python3 -m venv --help); bundled with standard CPython
  • pip 23 or newer (python3 -m pip install --upgrade pip)
  • A POSIX or PowerShell shell and an empty scratch project directory
  • Assumed skill: running commands from a terminal and editing text files

Step-by-Step Execution

1. Create and activate an isolated environment

python3.11 -m venv .venv && source .venv/bin/activate

Expected output:

No output; the shell prompt gains a (.venv) prefix after activation.

What this teaches: venv creation is silent by design. Activation only rewrites PATH and the prompt, so verification is a deliberate separate step. Fresh-system note: on Debian/Ubuntu, python3 -m venv needs the split-out package (sudo apt install python3-venv); without it you get "No module named venv".

2. Pin dependencies

pip install requests && pip freeze > requirements.txt

Expected output:

requirements.txt now lists requests plus pinned transitive deps (certifi, charset-normalizer, idna, urllib3).

What this teaches: pip freeze snapshots exact resolved versions including transitive dependencies, which is what lets another machine reproduce this environment. The pinned version numbers will differ from any example here depending on when you install.

3. Verify the interpreter and packages

python -c "import sys; print(sys.prefix)" && pip list

Expected output:

/home/taki/project/.venv
Package    Version
---------- -------
pip        23.3.1
requests   2.31.0
setuptools 68.2.2

What this teaches: sys.prefix reveals the active interpreter's home and pip list confirms the package set. Both must point inside .venv, never at the system Python. Version numbers and the exact package list vary by install date; the check is the location, not the versions.

How It Works

How It Works

A Python virtual environment is really just a directory holding its own interpreter reference, a private site-packages folder, and an activation script. Running python3 -m venv .venv creates that isolated directory tree; nothing else on the system changes.

Activation moves and reinstalls nothing. The activate script prepends the venv's bin directory to PATH and adjusts the prompt, so the names python and pip resolve inside .venv. Deactivating restores the previous PATH, which is why a venv is disposable: delete the folder and it is gone.

Pinning matters because pip install requests today and pip install requests next year can resolve different dependency versions. pip freeze records the exact resolved tree so requirements.txt reproduces the same set on any machine, which is the foundation of deterministic builds.

Validation & Testing

Recreate the environment from requirements on a clean directory and confirm identical package versions. Pass criteria: the venv activates, which python points inside .venv, and pip install -r reproduces the exact set.

Advanced: Troubleshooting
  • Wrong interpreter: confirm activation; which python must resolve inside the venv.
  • Conflicting versions: never pip install globally; always activate first.
  • Irreproducible builds: pin transitive deps with pip-tools or a lock file.

Troubleshooting

SymptomLikely causeFix
which python points outside .venvthe shell was not activated, or a new shell tab was openedrun source .venv/bin/activate in every new shell; confirm the prompt shows (.venv)
pip install fails with externally-managed-environmentsystem Python on Debian/Ubuntu blocks global installs (PEP 668)always install inside an activated venv instead of fighting the guardrail
requirements.txt missing transitive pinsthe file was hand-edited to list only direct depsregenerate with pip freeze > requirements.txt after installing; consider pip-tools for layered pins
venv breaks after moving the project folderactivation scripts and shebangs embed absolute pathsrecreate the venv in the new location instead of moving it
two projects need conflicting versionsa single global environment was used for bothone venv per project; this is the core habit this lab teaches

Key Results

  • Eliminated cross-project dependency conflicts via per-project venvs.
  • Made 100% of scripts reproducible from a pinned requirements file.
  • Standardized one project layout reused across 14 Python labs.
  • Cut environment setup to a single two-command bootstrap.

Key Takeaways

  • One project, one venv: isolation is the default, not the exception.
  • Pin the full tree with pip freeze so installs are deterministic on any machine.
  • Verify the interpreter with sys.prefix instead of assuming activation worked.
  • A venv is disposable: recreate it from requirements.txt rather than debugging a polluted one.
  • A standard layout (src/, requirements, .venv) makes every later lab start the same way.

Practice Challenges

Challenge 1: Bootstrap from scratch

Goal: Delete .venv and recreate the exact environment using only requirements.txt, then prove pip list matches the original.

Hint: The two commands from steps 1 and 2 are all you need; diff pip list output before and after.

Challenge 2: Catch a leak

Goal: With the venv active, explain why a package installed with pip install --target into system site-packages never shows up in pip list.

Hint: Inspect python -c "import sys; print(sys.path)" to see the isolation boundary.