Level 3 · Real Programs

Virtual Environments and pip 🧪

Half a million packages are waiting for you on PyPI. Two commands stop them from turning your computer into a swamp.

The problem, first

Project A needs version 1 of a library. Project B needs version 2. If both install into the same Python, one of them breaks. Multiply by twenty projects and several years and you get a machine where nothing works and nobody knows why. This has a name: dependency hell.

A virtual environment is a private Python for one project: its own folder, its own installed packages, isolated from everything else. Making one is one command, and you should make one for every project, every time, no exceptions.

The three commands

# 1. create it (once per project)
python3 -m venv .venv

# 2. activate it (every time you open a terminal)
source .venv/bin/activate        # macOS and Linux
.venv\Scripts\activate           # Windows PowerShell

# 3. install things (they land inside .venv, not on your system)
pip install requests

Once activated, your prompt changes to show it:

$ source .venv/bin/activate
(.venv) $ which python
/Users/you/python-school/.venv/bin/python

(.venv) $ pip install requests
Successfully installed requests-2.32.3 ...

(.venv) $ deactivate
$ 
📁 Call it .venv

The name is a convention, and a strong one: editors like VS Code look for .venv and offer to use it automatically, and every .gitignore template already excludes it. The leading dot hides it from ordinary directory listings.

Never commit it

# .gitignore
.venv/
__pycache__/
*.pyc
.env

A virtual environment contains thousands of files, is specific to your operating system, and can be rebuilt from a text file in seconds. Committing it to git is a classic beginner mistake that makes a repository unusably large.

Recording what you need

(.venv) $ pip freeze > requirements.txt
(.venv) $ cat requirements.txt
certifi==2025.7.9
charset-normalizer==3.4.2
idna==3.10
requests==2.32.3
urllib3==2.5.0

Anyone (including you, on another machine, in a year) can then recreate the exact environment:

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
VOLITION[Medium: Success]

This is the moment your code becomes shareable. Before a requirements file, 'it works on my machine' is a true statement and a useless one. After it, someone else can reproduce your machine in thirty seconds.

Reproducibility is not bureaucracy. It is the difference between a script and software.

pip, the essential commands

CommandDoes
pip install requestsInstall the latest version
pip install requests==2.32.3Install exactly that version
pip install 'requests>=2.30'Install at least that version
pip install -r requirements.txtInstall everything listed in a file
pip listWhat is installed here
pip show requestsDetails, including what depends on what
pip uninstall requestsRemove it
pip install --upgrade requestsUpdate it
🎯 Use python3 -m pip, not bare pip

python3 -m pip install x guarantees the package lands in the same Python you are running. Bare pip can belong to a different installation, which produces the single most baffling beginner experience there is: pip says it installed successfully, and Python says ModuleNotFoundError.

Using an installed package

# after: pip install requests
import requests

response = requests.get("https://api.github.com/repos/python/cpython")
data = response.json()

print(data["name"], "has", data["stargazers_count"], "stars")

That block has no run button, because the school's in-browser Python has no network access and no third-party packages. This is exactly the point at which doing the ten-minute lab setup starts to pay off. Lesson 42 covers requests properly.

The modern alternative: uv

uv is a drop-in replacement for pip and venv that is typically 10 to 100 times faster, because it is written in Rust. It is increasingly the default choice in new projects.

# the same three ideas, one tool
uv venv                       # create .venv
uv pip install requests       # install into it
uv run script.py              # run with the right environment, no activation needed

# or let it manage the whole project
uv init my-project
uv add requests
🦀 Wait, a Python tool written in Rust?

Yes, and it is one of the best arguments for learning both languages. uv, ruff (the linter) and Polars (dataframes) are all Rust programs that made the Python ecosystem dramatically faster. The pattern is: write the workflow in Python, write the hot inner loop in Rust. Lesson 51 shows you how to do it yourself, and the Rusty School is next door when you are ready.

Judging a package before you install it

Anyone can publish to PyPI. Packages have been published with names one typo away from popular ones, containing malware. Before installing something you have not heard of:

Common problems

SymptomCauseFix
ModuleNotFoundError right after installingInstalled into a different PythonActivate the venv, then python3 -m pip install x
externally-managed-environmentYou are installing into the system Python on Linux or HomebrewMake and activate a venv. The error is protecting you
command not found: pipNot activated, or pip not installedpython3 -m ensurepip, then activate
Works in the terminal, not in VS CodeThe editor is using a different interpreterCtrl+Shift+P, 'Python: Select Interpreter', pick the .venv one
Permission denied while installingTrying to write to a system folderNever use sudo pip. Use a venv
Exercise 1

Set up a real project

On your own machine, do the whole loop: folder, venv, activate, install requests, freeze, and confirm which Python is in charge.

Reveal solution
mkdir weather-tool && cd weather-tool
python3 -m venv .venv
source .venv/bin/activate

python -m pip install requests
python -c "import requests; print(requests.__version__)"

pip freeze > requirements.txt
printf '.venv/\n__pycache__/\n' > .gitignore

which python
deactivate

If which python printed a path ending in weather-tool/.venv/bin/python, everything is correct. That sequence is the opening move of every Python project you will ever start.

Exercise 2

Read a requirements file

What does each line mean, and which one would you object to in a code review?

requests==2.32.3
rich>=13.0
pandas
numpy~=1.26.0
Reveal solution
  1. requests==2.32.3: exactly this version. Fully reproducible.
  2. rich>=13.0: this or newer. A future version 14 could break you.
  3. pandas: any version at all. This is the one to object to. Today it installs 2.x; next year it installs 3.x and your program changes behaviour with no change to your code.
  4. numpy~=1.26.0: compatible release, so 1.26.x but not 1.27. A sensible middle ground.

Rule of thumb: pin exactly for applications you deploy, use ranges for libraries other people will install alongside things you cannot predict.

Exercise 3

Explain it to a colleague

A teammate says: 'Virtual environments are pointless, I just install everything globally and it works fine.' Write the three-sentence reply.

Reveal solution

Something like: It works fine until two projects need different versions of the same library, and then one of them breaks in a way that is genuinely hard to diagnose. It also means nobody else can reproduce your setup, so 'works on my machine' becomes the whole support process. A venv costs one command per project and removes both problems permanently.

The persuasive detail is the second sentence. Most people accept isolation as theory and adopt it for real the first time a colleague cannot run their code.

+100 XP