Skip to content

Python Virtual Environments & pip

A virtual environment is an isolated Python installation for a single project. It keeps each project’s packages separate so versions never clash, and it keeps your global Python clean. pip is the tool that installs and manages those packages. (For installing Python itself, see the Installation page.)

Golden rule: one virtual environment per project, and never pip install into your system Python.

diagram what a virtual environment actually changes mermaid
A venv is not a copy of Python. It is a directory with its own site-packages and a marker that tells the interpreter to look there, plus scripts that put its bin directory first on PATH. The interpreter itself is still the base one, which is why sys.base_prefix keeps pointing at the original install.

Without isolation, every project shares one set of packages:

  • Project A needs django==3.2, Project B needs django==4.2 — they can’t coexist globally.
  • A global install can break system tools that rely on specific versions.
  • You can’t reproduce “exactly what this project needs” for a teammate or server.

A virtual environment solves all three.

venv ships with Python. Create an environment (commonly named .venv) in your project folder:

terminal
# Create a virtual environment in the .venv folder
python -m venv .venv

This makes a .venv/ directory containing a private copy of Python and a place for packages.

You must activate the environment so python and pip point at it.

macOS / Linux
source .venv/bin/activate
Windows (PowerShell)
.venv\Scripts\Activate.ps1

Once active, your prompt usually shows (.venv). To leave it:

terminal
deactivate

Tip: add .venv/ to your .gitignore — environments are rebuilt from requirements.txt, not committed.

With the environment active:

terminal
pip install requests              # latest version
pip install "django==4.2"         # a specific version
pip install "flask>=2,<3"         # a version range
pip install --upgrade requests    # upgrade to the newest
pip uninstall requests            # remove a package
CommandDoes
pip install NAMEInstall the latest version.
pip install NAME==X.YInstall an exact version.
pip install --upgrade NAMEUpgrade a package.
pip uninstall NAMERemove a package.
pip listList installed packages.
pip show NAMEShow details about a package.

requirements.txt — reproducible installs

Section titled “requirements.txt — reproducible installs”

Record exact versions so anyone can recreate the environment.

terminal
# Save the current environment's packages
pip freeze > requirements.txt
 
# Recreate it elsewhere (in a fresh venv)
pip install -r requirements.txt

A requirements.txt looks like:

requirements.txt
requests==2.31.0
flask==3.0.0
python-dotenv==1.0.0
terminal
mkdir myproject && cd myproject
python -m venv .venv
source .venv/bin/activate          # (Windows: .venv\Scripts\Activate.ps1)
pip install requests flask
pip freeze > requirements.txt
# ... write code ...
deactivate
  • Forgetting to activatepip install then lands in global Python. Check your prompt for (.venv).
  • Committing .venv/ — it’s large and machine-specific; commit requirements.txt instead.
  • Mixing pip and the system package manager — inside a venv, always use pip.
  • python vs python3 — on some systems the command is python3; the same goes for pip/pip3.

These run plain Python that mirrors what the tools do under the hood.

Exercise 1 – Check the running Python version

Section titled “Exercise 1 – Check the running Python version”

Exercise 3 – Build a pip install command

Section titled “Exercise 3 – Build a pip install command”
sketch A venv is a directory and two variables, not a copy of Python p5.js
The interpreter is still the base one -- that is why sys.base_prefix keeps pointing at the original install. What changes is sys.prefix, and therefore where imports are found. Activation only edits PATH, which is why running the venv's interpreter by full path works whether or not you activated anything.
pch.quizTag pch.quizDefaultTitle
  1. How do you reliably detect that code is running inside a virtual environment?

    pch.quizShowAnswer

    B — Compare `sys.prefix` with `sys.base_prefix` — Verified on both interpreters here. `VIRTUAL_ENV` is set by the activate SCRIPT, so it is absent when a cron job or service runs the venv's interpreter by full path — which is the usual deployment shape.

  2. What does `sys.base_prefix` point at inside a venv?

    pch.quizShowAnswer

    B — The original Python installation — A venv is not a copy of Python — the interpreter is still the base one. That is exactly what makes the two-variable comparison work as a test.

  3. What does activating a virtual environment actually do?

    pch.quizShowAnswer

    B — Puts the venv's script directory first on `PATH` — It is PATH manipulation plus a prompt change. `/path/.venv/bin/python script.py` uses the venv with no activation at all.

  4. Why does a cron job often fail with `ModuleNotFoundError` when the same command works in your shell?

    pch.quizShowAnswer

    B — Cron does not run your shell, so no venv is activated and PATH is minimal — Use the absolute interpreter path — `/path/.venv/bin/python` — rather than relying on activation. The same reasoning covers the working directory, which is HOME rather than your project.

  • A virtual environment isolates one project’s packages; create it with python -m venv .venv.
  • Activate it before working (source .venv/bin/activate or Activate.ps1), deactivate to leave.
  • Use pip to install, upgrade, uninstall, and list packages.
  • Pin versions with pip freeze > requirements.txt and restore with pip install -r requirements.txt.
  • Commit requirements.txt, not the .venv/ folder.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading