Command Palette

Search for a command to run...

Hectal
PHASE 6Intermediate ~29 min· topic 5 of 9

Topic 6.5

Virtual Environments and pip

In one line

A virtual environment is a private folder of installed packages for one project, so projects with different needs do not fight. pip installs packages from PyPI into it, and a requirements.txt file records what to install again.

Think of it like this

Lunchboxes. If everyone in the family keeps food in one big shared box, one person's spicy pickle ruins another's sweets, and nobody knows whose is whose. Give each person a labelled lunchbox and nothing mixes. A virtual environment is a labelled lunchbox for one project's packages.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Package
Code published for others to install and import, such as numpy.
PyPI
The Python Package Index at pypi.org, the public shop that pip downloads from.
pip
Python's standard installer for packages.
Virtual environment (venv)
A private folder with its own Python and its own installed packages, made for one project.
Activate
Make the current terminal use the venv's python and pip.
site-packages
The folder inside an environment where installed packages are stored.
requirements.txt
A text file listing the packages (and versions) a project needs.
Pinning
Fixing a dependency to an exact version, such as requests==2.32.3.
Dependency
A package your code needs in order to run.
Terminal
A window where you type commands as text. Also called the command line. The program reading your commands is the shell.
Interpreter
The program that reads your Python code and runs it. The command python starts it.
PATH
A list of folders your terminal searches when you type a command such as python. The first match wins.
Folder / directory
A place that holds files. Two words for the same thing.

Step by step

01The problem: one shared place

Imagine two projects. The older one needs libX version 1; the newer one needs version 2. Installed into the same Python, only one can win, and the other breaks. A venv per project gives each its own copy.

The problem: one shared placediagram
Rendering diagram…

02Create and activate

python -m venv .venv runs the standard-library venv module and creates the .venv folder. Activation changes your shell for that terminal window only, so each new terminal needs it again (editors like VS Code do it for you once you select the interpreter).

The (.venv) in the prompt is the reminder that you are "inside".

Windows (cmd or PowerShell)whole filebash
python -m venv .venv
.venv\Scripts\activate          # cmd.exe
.venv\Scripts\Activate.ps1      # PowerShell
terminal
$ python -m venv .venv
source .venv/bin/activate
── expected output ──
(.venv) $

03Windows: two common snags

If python is not found on Windows, try py -m venv .venv. py is the launcher that the python.org installer adds. If PowerShell says that running scripts is disabled when you activate, either use Command Prompt (cmd) for this step, or allow local scripts for your user once with Set-ExecutionPolicy -Scope CurrentUser RemoteSigned. It changes a Windows safety setting, so read the message and only do it on your own computer.

04Install packages

With the venv active, python -m pip install httpx downloads httpx and everything it depends on from PyPI and unpacks them into .venv/.../site-packages. pip list shows what is there. pip uninstall httpx removes it.

Check which Python you are using: which python (macOS/Linux) or where python (Windows) should print a path inside .venv. The terminal finds python by searching the folders on its PATH in order, and activation puts .venv's folder first.

terminalwhole filebash
python -m pip install httpx
python -m pip list
python -c "import httpx; print(httpx.__version__)"

05Record and rebuild: requirements.txt

pip freeze prints every installed package with its exact version. Redirect it into a file and commit that file. A colleague, a CI server or a Docker build then runs one command to get the same set.

pip freeze lists *everything*, including packages pulled in indirectly. Many teams keep a short hand-written list of direct dependencies and let a tool produce the full pinned list (Topic 6.6 and Topic 6.7).

terminalwhole filebash
python -m pip freeze > requirements.txt
python -m pip install -r requirements.txt

06Why .venv is never committed

A .venv folder holds thousands of files and absolute paths specific to your machine, so it cannot be copied to another computer. What you commit is the *recipe*: requirements.txt (or pyproject.toml and a lock file, Topic 6.7). Add .venv/ to .gitignore (Topic 6.8).

07Reading an import failure

The commonest beginner failure is ModuleNotFoundError for a package you *did* install. There are three usual causes: the venv is not activated, you installed with a different pip than the python you run (use python -m pip), or your editor uses a different interpreter than your terminal. The first check is always: which python am I running, and does it have the package?

main.pywhole filepython
import sys

print(sys.executable)     # the exact interpreter running this file
print(sys.prefix != sys.base_prefix)   # True inside a venv

08The 'externally managed' error

On a system-managed Python, a plain global install is blocked, and pip tells you so and suggests a virtual environment. The correct response is to create a venv for the project. For command-line tools you want everywhere (like ruff, a tool that checks your code style), pipx or uv tool install give each tool its own hidden venv.

terminalwhole filebash
$ pip install requests
error: externally-managed-environment

× This environment is externally managed

Try it yourself

  1. 1

    Make your first venv

    On your computer, make a folder, run python -m venv .venv, activate it, and run python -c "import sys; print(sys.prefix != sys.base_prefix)". It should print True. Run deactivate and repeat. It prints False.

  2. 2

    Install and prove it is isolated

    Inside the venv run python -m pip install httpx, then python -c "import httpx" (it works). Deactivate and run the same line again. You should get ModuleNotFoundError, because the package lives only in .venv.

  3. 3

    Read the classification

    In the second example above, add the line black>=24 to the requirements and run it. Predict its label first. Then add httpx[http2]>=0.27 and look at the name column: the extras part [http2] (an optional bundle of extra dependencies) ends up inside the name. That is one reason a hand-written parser is not a substitute for pip's own.

Code & diagrams

Is this Python in a venv, and what is importable? New tab

Here the answer to the first question is `False`, because this page runs in your browser, not in a venv. On your computer, run the same code inside an activated venv and `True` appears. `find_spec(name)` asks Python whether it can find a module of that name, without importing it (it gives `None` when it cannot). The last line shows the error you get for a package that is not installed.

Sign in to run this example in your browser.

Expected output

running inside a virtual environment: False
json: importable
math: importable
definitely_not_installed_pkg: NOT installed
Traceback (most recent call last):
  File "main.py", line 11, in <module>
    import definitely_not_installed_pkg
ModuleNotFoundError: No module named 'definitely_not_installed_pkg'
Reading a requirements.txt New tab

A tiny reader that mirrors what `pip` does with each line: skip comments and blanks, split a name from its rule, and label how strict the rule is. The real parser is far more complete.

Sign in to run this example in your browser.

Expected output

requests  ==2.32.3       pinned exactly
numpy     >=1.26,<3      a range
pandas    (any version)  unpinned
The whole workflowbash

In a new terminal, repeat step 2 only. On another computer: create, activate, then `python -m pip install -r requirements.txt`.

python -m venv .venv                    # 1. create
source .venv/bin/activate               # 2. activate (Windows: .venv\Scripts\activate)
python -m pip install httpx pydantic    # 3. install
python -m pip freeze > requirements.txt # 4. record
deactivate                              # 5. leave
One interpreter per projectdiagram
Rendering diagram…

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Install globally on a protected Python

Run pip install requests without a virtual environment on a recent Debian, Ubuntu or Homebrew Python.

terminal
$ pip install requests
── what you'll see ──
error: externally-managed-environment
 
× This environment is externally managed

Break #2

Installed it, but cannot import it

Install with one pip but run a different python.

terminal
$ python main.py
── what you'll see ──
Traceback (most recent call last):
File "main.py", line 1, in <module>
import httpx
ModuleNotFoundError: No module named 'httpx'

Myth vs fact

Myth

A virtual environment is a virtual machine.

Fact

It is just a folder with a Python launcher and a site-packages directory. It costs almost nothing and starts instantly.

Myth

I can move or copy a .venv folder to another computer.

Fact

It contains paths and compiled files specific to your machine. Rebuild it from requirements.txt or a lock file instead.

Myth

pip install installs the newest version, so my code always works.

Fact

A new release can change behaviour. Pin versions (or use a lock file) so every machine gets the same code.

Myth

If a package is on PyPI, it is safe.

Fact

Anyone can publish to PyPI. Check the name and the project page before installing (Topic 6.9).

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    pip does not, by itself, give you a reproducible build. pip freeze captures versions but not hashes, and a loose requirements.txt resolves differently next month. Use pinned versions with hashes (pip install --require-hashes) or a lock file from uv (Topic 6.6) when the environment matters, such as in production images.

  • ▸

    Dependency resolution is a hard problem: package A needs x<2, B needs x>=2. Modern pip has a real resolver (the part that works out which versions can live together) and reports conflicts, but the answers can be slow. uv solves the same problem much faster and writes the result into a lock file.

  • ▸

    Compiled packages ship as wheels (Topic 6.9) built for a specific operating system and Python version. If no wheel matches your platform, pip falls back to compiling from source, which needs a compiler and often fails with long error messages. A different Python version (say, a brand new one) is the usual reason a wheel is missing.

  • ▸

    Activation is only a convenience: it puts .venv/bin (or .venv\Scripts) first on PATH. Running .venv/bin/python main.py directly uses the venv with no activation, which is what cron jobs, systemd services and CI steps should do. A venv is found by the pyvenv.cfg file beside the interpreter, which also records the base Python it was made from; if that base Python is deleted or upgraded in place, the venv breaks and should be recreated. By default a venv does not see the base Python's installed packages (--system-site-packages changes that). On Debian and Ubuntu, python -m venv can fail until the separate python3-venv system package is installed.

  • ▸

    In Docker and CI the venv is often skipped or created inside the image, since the container already isolates the whole system. The principle holds: one environment per application, built from a recorded recipe.

Remember this

  1. 1

    Most useful Python code comes from outside the standard library: numpy, httpx, pydantic. These are packages, published on PyPI (the Python Package Index, at pypi.org), a public shop where anyone can upload code. pip is the installer that downloads a package from PyPI and puts it where Python can import it (the site-packages folder).

  2. 2

    A word about where these commands go. From here on, many lessons use the terminal: a window where you type a command as text and press Enter. (Topic 0.4 showed you how to run a file from it.) On Windows, search for "Terminal", "PowerShell" or "Command Prompt". On macOS, open the app "Terminal". On Linux, press Ctrl+Alt+T. In VS Code, use the menu Terminal, then New Terminal. The program that reads what you type is called the shell. A folder (also called a directory) is where files live, and cd name means "go into the folder called name". Lines that start with $ in this course show the prompt, so do not type the $. Text after a # is a comment for you, and the shell ignores it.

  3. 3

    If you install everything into the one Python that came with your computer, trouble starts quickly. Project A needs version 1 of a library, project B needs version 2, and both cannot be installed in the same place. Upgrading for one breaks the other. Operating systems also use their own Python for their own tools, and installing into it can break your system. Isolation solves both problems.

  4. 4

    A virtual environment (a venv) is a folder, usually named .venv inside the project, containing a copy of (or link to) the Python interpreter (the program that reads and runs your code, Topic 0.8) and its own empty site-packages. When it is *activated*, the commands python and pip refer to that folder's versions, so packages you install land inside it and nowhere else. Delete the folder and the environment is gone, and the project's code is untouched.

  5. 5

    The workflow is always the same. (1) Create: python -m venv .venv. (2) Activate it in each new terminal: source .venv/bin/activate on macOS and Linux (source runs a script inside the current terminal), .venv\Scripts\activate on Windows (your prompt then shows (.venv)). (3) Install: python -m pip install requests. (4) Record what you installed in a requirements.txt file. (5) Leave with deactivate. Writing python -m pip instead of bare pip guarantees that the installer matches the Python you are running.

  6. 6

    A requirements.txt is a plain list, one package per line, usually with versions: requests==2.32.3 means exactly that version; numpy>=1.26,<3 means a range; a bare pandas means any version. pip freeze > requirements.txt writes everything currently installed, with exact versions. Someone else (or a server) rebuilds the environment with pip install -r requirements.txt. Pinning exact versions makes the rebuild reproducible; leaving them loose means "whatever is newest today", which changes without warning.

  7. 7

    Many new Linux distributions and Homebrew Pythons refuse global installs with the message externally-managed-environment (PEP 668). (A PEP is a numbered Python design document.) It is not a bug: the system is protecting its own Python. The answer is the one this topic teaches: create a venv and install there. Never override the protection with --break-system-packages unless you understand the risk. Next topic (6.6) shows uv, which does all of this faster and with one tool.

Explain it without notes

01

Why do we use virtual environments?

02

What happens when you activate a venv, and what does python -m pip guarantee?

03

What is requirements.txt, and what is the difference between ==, >= and no version?

04

What does the error externally-managed-environment mean, and what should you do?

Practice

01

Write the five terminal commands for macOS or Linux, one per line and in order: create a venv named .venv, activate it, install the package rich, record the installed packages in requirements.txt, and deactivate.

02

Write a function is_pinned(line) that takes one requirements line such as "requests==2.32.3" and returns True only when the line contains ==. Print the result for "requests==2.32.3" and for "numpy>=1.26".

03

Write a program that prints inside venv when sys.prefix is different from sys.base_prefix, and outside venv when they are equal.

Trade-offs

  • ↔

    One shared environment vs one per project. A shared one is less to manage but breaks as soon as two projects disagree. Per-project environments cost a little disk space and one extra command, and save hours of debugging.

  • ↔

    **pip freeze vs a hand-written requirements list.** Freeze is exact but long, and mixes your choices with pulled-in packages. A short list is readable but resolves differently over time. Lock files give both: a short list of intentions plus an exact record of the result.

Done when you can

  • Done when you can create, activate and leave a venv on your operating system.

  • Done when you can install a package with python -m pip and say why that form is safer.

  • Done when you can read and write a requirements.txt with ==, a range and no version.

  • Done when you can explain why .venv is not committed to git.

  • Done when you can explain the externally-managed error and what to do about it.