Title: Lecture 7 — Isolating and Reproducing Your Python Setup
Course: AIINFRA 100: AI Systems & Development Environments > Section: Week 09 — Environment & Package Management: venv, conda, uv

📖 Lecture — Isolating and Reproducing Your Python Setup

If you've ever installed a package for one project and watched it break a completely different project, you've already met "dependency hell." The fix the whole Python ecosystem agrees on is the virtual environment: a self-contained folder holding its own Python interpreter (or a pointer to one) plus its own installed packages, kept separate from your system Python and from every other project on your machine. When you "activate" an environment, your terminal points at that isolated set of packages instead of the global one. Once you deactivate it, everything returns to normal. This one habit — always work inside an activated environment — prevents the single most common beginner headache in Python development. There are three tools you'll meet this week, and they solve overlapping but distinct problems. venv is Python's built-in environment tool — nothing to install, since it ships with Python 3 itself. Running python -m venv myenv creates a folder called myenv containing a private copy of the interpreter and an empty site-packages directory. You activate it (source myenv/bin/activate on macOS/Linux, myenv\Scripts\activate on Windows), and from then on pip install puts packages inside that folder only. venv is lightweight and perfect for pure-Python projects, but it only manages Python packages — it has no idea about non-Python dependencies like CUDA libraries or system compilers. conda is a cross-platform package and environment manager. Unlike venv, conda can install and manage non-Python dependencies too — C libraries, compilers, even CUDA toolkits for GPU work — which is why it's popular in data science and machine learning, where a package like PyTorch depends on system-level binaries, not just Python code. conda create -n myenv python=3.11 creates a new environment with its own Python version (not just a copy of your system one), and conda activate myenv switches into it. pip is simply a package installer — it downloads and installs packages from PyPI, but it has no built-in concept of isolation. That's why pip relies on venv or conda to provide the isolated folder it installs into. "pip install requests" run outside an activated environment silently installs into whatever environment (or your system Python) happens to be active — which is a very common source of confusion. uv, from the company Astral, is a newer, Rust-based tool built for speed and simplicity. It is a drop-in replacement that can substitute for pip, pip-tools, virtualenv, and even pyenv, all through one unified command-line interface. Benchmarks commonly show it running 10–100x faster than pip for installs and dependency resolution, because it's written in Rust and uses aggressive caching and parallelism. For beginners, the appeal is fewer tools to remember: uv init, uv add requests, and uv run handle environment creation, package installation, and execution without you ever manually activating anything. The table below summarizes how the three compare:

Tool | What it manages | Handles non-Python deps? | Speed | Best for
venv | Python environment isolation only | No | Standard (uses pip) | Simple, pure-Python projects; no install needed
conda | Environments AND packages (Python + non-Python) | Yes (C libs, CUDA, compilers) | Slower installs, solid isolation | Data science / ML stacks with system-level dependencies
uv | Environments AND packages (pip-compatible) | No (Python-focused) | Very fast (10–100x pip) | Modern Python projects wanting speed + simplicity

Reproducibility: requirements.txt vs. lock files. The classic way to share your setup is pip freeze > requirements.txt, which writes out every installed package and its exact version. That's better than nothing, but it's an older mechanism with a real weakness: it only records what pip freeze saw at that moment, and it doesn't capture cryptographic hashes or guarantee the same transitive dependencies resolve the same way later. Modern tools solve this with true lock files — uv.lock (from uv) or a pinned environment.yml (from conda env export) — which pin the exact resolved versions of every direct and transitive dependency, so the environment can be rebuilt identically on any machine, months later. This points to an important workflow distinction: your pyproject.toml (uv) or environment.yml (conda) declares loose constraints — "I want requests, version 2.x or higher" — describing what you want. Your lock file (uv.lock, or a pinned/exported environment.yml) pins exact versions — describing what was actually installed. Both files should be committed to version control, but for different reasons: the constraints file documents intent and allows some flexibility when you next update; the lock file guarantees anyone else (a teammate, a grader, your future self) gets byte-for-byte the same environment. Correcting two common misconceptions:

1. "Running pip install X installs X globally and permanently fixes the problem." Not true — pip always installs into whichever environment is currently active. If you forget to activate your project's environment first, pip quietly installs into your system Python (or whatever environment happened to be active), polluting it and potentially breaking unrelated projects. Always confirm your environment is activated (check your terminal prompt for the environment name) before you run pip.
2. "A requirements.txt file alone guarantees reproducibility." Not true either — unless every version is explicitly pinned (and ideally hashed), pip install -r requirements.txt run months later can resolve slightly different transitive dependency versions than you had, because requirements.txt typically doesn't lock the entire dependency tree the way a real lock file (uv.lock) does. For guaranteed reproducibility, prefer a genuine lock file whenever your tooling supports one.

The standard CTE (career-technical-education) workflow you'll practice in this week's lab is: create environment → activate → install packages → freeze/lock → share the snapshot file so a teammate or grader can reproduce your exact setup with one command.