Virtual environments and pip

Intermediate40 min

Create an isolated environment for each project, install third-party packages into it with pip, and record what you installed so someone else can repeat it.

What you will learn
  • Create and use a virtual environment with python -m venv
  • Install, list, upgrade and remove packages with pip
  • Explain why a system Python often refuses pip installs
  • Record dependencies in requirements.txt and rebuild an environment from it
On this page

The standard library will take you far, but sooner or later you want a package someone else published. Installing it for the whole machine causes trouble: one project needs version 1 of a library, another needs version 2, and an upgrade for one silently breaks the other. A virtual environment gives each project its own private set of installed packages.

The commands in this lesson run in a terminal, not inside Python. Blocks marked console are sessions to type yourself. The site’s checks do not run them. If a terminal still feels new, the fundamentals lesson on tools covers it.

What a virtual environment is

A virtual environment is a folder containing a Python interpreter and its own site-packages directory, the place where installed packages go. The Python documentation describes one as disposable: it should be easy to delete and recreate, it is not checked into version control, and it is not meant to be moved or copied.

Create one inside your project folder, conventionally named .venv:

Terminal window
$ python -m venv .venv

On Windows the launcher is often py, so the command is py -m venv .venv. Either way, python -m venv runs the standard library’s venv module, and .venv is the folder it makes. Inside you will find a bin folder (Scripts on Windows) with the interpreter and pip, and a lib/pythonX.Y/site-packages folder (Lib\site-packages on Windows) that starts nearly empty.

Activating it

Activating puts the environment’s bin folder first on your PATH, so the commands python and pip mean the environment’s own.

macOS and Linux (bash, zsh)

Terminal window
$ source .venv/bin/activate
(.venv) $

Windows PowerShell

Terminal window
PS> .venv\Scripts\Activate.ps1
(.venv) PS>

Windows cmd.exe

Terminal window
C:\> .venv\Scripts\activate.bat
(.venv) C:\>

The (.venv) prefix reminds you that you are inside. To leave, type deactivate.

Activation is a convenience, not a requirement. You can always call the environment’s interpreter by its path, as in .venv/bin/python script.py, and it behaves the same way. That is how editors and scheduled jobs usually do it.

To check which Python you are really running, ask the interpreter. In a virtual environment, sys.prefix points at the environment and sys.base_prefix points at the Python it was built from, so they differ.

where_am_i.py
import sys
if sys.prefix != sys.base_prefix:
print("virtual environment:", sys.prefix)
else:
print("base installation:", sys.prefix)
Terminal window
$ python where_am_i.py
virtual environment: /home/you/project/.venv

Outside the environment, the same script prints base installation: followed by the path of your main Python.

Installing with pip

pip is Python’s package installer, and it comes with the environment. Always run it as python -m pip, which guarantees it belongs to the same interpreter you will run your code with. Here we install requests, a widely used library for HTTP:

Terminal window
(.venv) $ python -m pip install requests
Collecting requests
...
Installing collected packages: urllib3, idna, charset-normalizer, certifi, requests
Successfully installed certifi-... charset-normalizer-... idna-... requests-... urllib3-...

The output is trimmed here and the version numbers elided, since yours will differ. requests depends on four other packages, and pip installed them all. Now the import works inside the environment:

check_requests.py
import requests
print(requests.codes.ok)
Terminal window
(.venv) $ python check_requests.py
200

This script needs requests installed, so it is the one example in this module that the site’s automatic checks skip. Outside the environment it would stop with ModuleNotFoundError: No module named 'requests', which is the expected way for isolation to look.

A few other commands you will use constantly:

Terminal window
$ python -m pip list show what is installed
$ python -m pip install "requests>=2" install with a version constraint
$ python -m pip install --upgrade requests
$ python -m pip uninstall requests
$ python -m pip show requests version, dependencies, install location

Recording what you installed

If you hand your project to someone else, they need to know what to install. pip freeze prints every installed package with its exact version, in a form pip can read back:

Terminal window
(.venv) $ python -m pip freeze > requirements.txt
(.venv) $ cat requirements.txt
certifi==...
charset-normalizer==...
idna==...
requests==...
urllib3==...

Anyone, including you in six months, can rebuild the environment from that file:

Terminal window
$ python -m venv .venv
$ source .venv/bin/activate
(.venv) $ python -m pip install -r requirements.txt

This is why disposable is a feature. If the environment breaks, delete .venv and recreate it from the requirements file. The next lessons describe a more capable way to record dependencies, in pyproject.toml, and a faster installer, uv. The ideas carry over unchanged.

Why the system Python says no

On many Linux distributions and on some macOS setups, running pip install against the system Python fails with an error that the environment is externally managed. This is PEP 668: the operating system’s package manager owns that Python, and a second installer writing into the same folders could break system tools. The cure the message points to is the one you have already learned: make a virtual environment and install there. Do not reach for sudo pip, and do not override the protection with --break-system-packages unless you know exactly what you are doing.

ExerciseInside or outside?

Run where_am_i.py twice, once with the environment activated and once after deactivate. Then run it a third time without activating, but by calling the environment’s interpreter directly by path. Which of the three print “virtual environment”?

Expected: three lines, one per run in the order described, each reading virtual environment: ... or base installation: ....

Show solution

The first and the third:

Terminal window
$ source .venv/bin/activate
(.venv) $ python where_am_i.py
virtual environment: /home/you/project/.venv
(.venv) $ deactivate
$ python where_am_i.py
base installation: /usr
$ .venv/bin/python where_am_i.py
virtual environment: /home/you/project/.venv

The interpreter at .venv/bin/python knows it lives in an environment from its own location, so activation is not needed. Activation only changes what the bare word python means in your shell. On Windows the path is .venv\Scripts\python.exe.

ExerciseCreate, install, freeze, rebuild

In a new empty folder, create a virtual environment, install requests, and run check_requests.py. Write requirements.txt with pip freeze. Then delete .venv entirely, recreate it, install from the requirements file, and run the script again. It should print 200 both times.

Expected: requirements.txt contains a line starting requests==, and the two runs of the script print:

200
200
Show solution
Terminal window
$ python -m venv .venv
$ source .venv/bin/activate
(.venv) $ python -m pip install requests
(.venv) $ python check_requests.py
200
(.venv) $ python -m pip freeze > requirements.txt
(.venv) $ deactivate
$ rm -rf .venv
$ python -m venv .venv
$ source .venv/bin/activate
(.venv) $ python -m pip install -r requirements.txt
(.venv) $ python check_requests.py
200

On Windows, delete the folder with rmdir /s /q .venv in cmd.exe, and use the Windows activation command from the tabs. The point is that the environment is rebuilt from the text file alone, with no state kept in .venv.

ExerciseInstalled it, still can't import it

A learner runs pip install requests and sees “Successfully installed”. They then run python check_requests.py and get ModuleNotFoundError: No module named 'requests'. Give two likely causes and the single command that settles it.

Expected: two causes in one sentence each, and one command line of the form python -m pip install requests, after which python check_requests.py prints 200.

Show solution

The pip that ran and the python that ran belong to different interpreters. Typical causes: the environment was active in one terminal window but the script was run from another, or the bare pip on the PATH belongs to a different Python installation than the bare python.

The command that settles it is the one that ties the two together:

Terminal window
$ python -m pip install requests

python -m pip installs into the interpreter named by python, so the same python finds the package afterwards. To see which interpreter you have, run python -c "import sys; print(sys.executable)".

Summary

  • A virtual environment is a disposable folder of installed packages, made with python -m venv .venv, used by activating it or by calling its interpreter by path.
  • Install with python -m pip install, so pip and python are the same interpreter. Record with pip freeze > requirements.txt and restore with pip install -r.
  • A system Python marked externally managed (PEP 668) is a reason to use a virtual environment, not to override the warning.