Chapter 15: Virtual Environments and pip¶
1. Overview¶
When you install a Python package with pip install requests, it lands in
your system-wide Python installation. That works fine for a single project,
but the moment you have two projects that need different versions of the same
library — or you want to share your project with someone else and guarantee
they get exactly the same dependencies — the global approach breaks down.
Virtual environments solve this by giving each project its own isolated Python environment: its own copy of pip, its own installed packages, and no interference with anything else on your machine. They are a fundamental part of everyday Python work, and learning to use them early will save you a lot of confusion later.
This chapter covers everything you need to go from a bare Python installation to a fully reproducible project environment.
2. What You Will Learn¶
- Why virtual environments exist and why you should always use them
- Creating a virtual environment with
python -m venv - Activating and deactivating environments on Windows, macOS, and Linux
- Installing, upgrading, and removing packages with
pip - Listing installed packages with
pip listandpip freeze - Generating a
requirements.txtfile - Recreating an environment from
requirements.txt - Pinning package versions for reproducibility
- Keeping pip itself up to date
- What to add to
.gitignore - A brief look at modern tooling (uv, Poetry) as a next step
3. Core Concepts¶
3.1 Why Virtual Environments Exist¶
Python packages are installed into a specific location on your filesystem. Without virtual environments, every package goes into the same global location. This creates two problems.
Version conflicts. Project A needs requests==2.28.0 and Project B needs
requests==2.31.0. You cannot have both installed globally at the same time.
Upgrading for one project breaks the other.
Reproducibility. When you share your project, the other person needs to know exactly which packages to install. If you have been installing things globally over months, there is no clean record of what your project actually needs.
A virtual environment is a self-contained directory that holds:
- A copy of (or a symlink to) the Python interpreter
- Its own
pip - Its own
site-packagesdirectory where installed packages live
Each project gets its own environment. Packages installed in one environment are invisible to all others.
3.2 Creating a Virtual Environment¶
Python 3.3+ ships with the venv module in the standard library. No
installation required.
This creates a directory called venv in your current folder. The name
venv is a convention — you can call it anything, but venv and .venv are
the two most common choices.
my-project/
venv/ ← the virtual environment lives here
bin/ ← (macOS/Linux) Python, pip, activate script
Scripts/ ← (Windows) Python, pip, activate script
lib/
python3.x/
site-packages/ ← installed packages go here
main.py
requirements.txt
The venv directory is generated — you never edit it by hand, and you never
commit it to version control.
Specifying a Python version¶
If you have multiple Python versions installed, pass the interpreter you want:
On Windows you may need to use the py launcher:
3.3 Activating the Environment¶
Creating the environment does not automatically use it. You need to
activate it, which adjusts your shell's PATH so that python and pip
refer to the environment's copies rather than the system ones.
macOS and Linux¶
Windows (Command Prompt)¶
Windows (PowerShell)¶
Note for PowerShell users: If you see an error about execution policy, run
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUseronce to allow local scripts to run.
After activation, your prompt changes to show the environment name:
Now python and pip point to the environment:
which python # macOS/Linux: .../my-project/venv/bin/python
where python # Windows: ...\my-project\venv\Scripts\python.exe
Everything you install with pip from this point goes into the environment,
not the global Python installation.
3.4 Deactivating the Environment¶
When you are done working on the project, deactivate the environment to return to your normal shell:
Your prompt returns to normal and python / pip point back to the system
installation.
You do not need to deactivate before switching to another project — you can just activate a different environment and it will replace the current one.
3.5 Installing Packages with pip¶
With the environment active, install packages using pip install:
Collecting requests
Downloading requests-2.31.0-py3-none-any.whl (62 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 62.6/62.6 kB 1.2 MB/s eta 0:00:00
Collecting charset-normalizer<4,>=2 (from requests)
...
Successfully installed certifi-2024.2.2 charset-normalizer-3.3.2 idna-3.6 requests-2.31.0 urllib3-2.2.1
pip automatically installs any packages that requests depends on
(its dependencies).
Installing a specific version¶
Installing multiple packages at once¶
Installing a minimum version¶
Upgrading an already-installed package¶
Uninstalling a package¶
pip will ask for confirmation before removing the package.
3.6 Listing Installed Packages¶
pip list¶
Shows all installed packages in a readable table:
Package Version
------------------ ---------
certifi 2024.2.2
charset-normalizer 3.3.2
idna 3.6
pip 24.0
requests 2.31.0
urllib3 2.2.1
pip freeze¶
Shows installed packages in a format suitable for a requirements file:
Note that pip freeze does not include pip itself. It only lists packages
that were installed into the environment (not the tools that come with it).
Checking a single package¶
Name: requests
Version: 2.31.0
Summary: Python HTTP for Humans.
Home-page: https://requests.readthedocs.io
Author: Kenneth Reitz
License: Apache 2.0
Location: .../venv/lib/python3.11/site-packages
Requires: certifi, charset-normalizer, idna, urllib3
Required-by:
3.7 The requirements.txt File¶
A requirements.txt file is a plain text list of packages (and their
versions) that your project needs. It is the standard way to record and share
dependencies.
Generating requirements.txt¶
This redirects the output of pip freeze into a file. The result looks like:
Run this command whenever you add or upgrade a package so the file stays up to date.
Installing from requirements.txt¶
When someone else clones your project (or when you set it up on a new machine), they recreate the environment like this:
python -m venv venv
source venv/bin/activate # or venv\Scripts\activate on Windows
pip install -r requirements.txt
The -r flag tells pip to read the file and install everything listed in it.
This gives them exactly the same package versions you used.
3.8 Pinning Versions¶
Pinning means specifying an exact version number in requirements.txt using
==. This is what pip freeze produces automatically.
Why pin? If you write requests without a version, pip installs the
latest version available. That is fine today, but six months from now the
latest version might have breaking changes. Pinning ensures that anyone who
installs your requirements gets the exact same versions you tested with.
Direct vs. transitive dependencies. Your project directly depends on
requests. But requests itself depends on certifi, charset-normalizer,
idna, and urllib3. These are transitive dependencies — you did not
ask for them, but they are required. pip freeze captures all of them,
which is why the file is longer than just the packages you explicitly
installed.
Some teams prefer to keep two files:
requirements.in— only the packages you directly depend on, with loose version constraintsrequirements.txt— the full pinned output frompip freeze, used for reproducible installs
Tools like pip-tools can manage this workflow, but for most beginner
projects a single requirements.txt from pip freeze is sufficient.
3.9 Upgrading pip¶
pip itself is a package and can become outdated. You may see a warning like:
WARNING: You are using pip version 23.0; however, version 24.0 is available.
You should consider upgrading via the 'python -m pip install --upgrade pip' command.
Upgrade pip inside the active environment:
Using python -m pip (rather than just pip) ensures you are upgrading the
pip that belongs to the current environment, not some other one on your
system.
3.10 Keeping venv/ Out of Version Control¶
The venv/ directory is generated from requirements.txt. It is large,
platform-specific, and contains no source code you wrote. Never commit it to
git.
Add it to your .gitignore:
Both venv/ and .venv/ are common names — add both to be safe. GitHub's
default Python .gitignore template already includes these entries, so if
you initialize a repository with the Python template you are covered.
What you do commit:
requirements.txt— so others can recreate the environment- Your source code
- Configuration files (
pyproject.toml,setup.cfg, etc.)
What you do not commit:
venv/or.venv/__pycache__/directories.pycfiles
4. Practical Examples¶
4.1 Starting a New Project from Scratch¶
Here is the complete workflow for starting a new project:
# 1. Create the project directory
mkdir my-project
cd my-project
# 2. Create a virtual environment
python -m venv venv
# 3. Activate it
source venv/bin/activate # macOS/Linux
# venv\Scripts\activate # Windows
# 4. Upgrade pip
python -m pip install --upgrade pip
# 5. Install your dependencies
pip install requests
# 6. Write your code
# ... (create main.py, etc.)
# 7. Save your dependencies
pip freeze > requirements.txt
# 8. Initialize git and add .gitignore
git init
echo "venv/" >> .gitignore
echo "__pycache__/" >> .gitignore
git add .
git commit -m "Initial commit"
4.2 A Simple Script Using an Installed Package¶
With requests installed in the active environment:
# main.py
import requests
def get_public_ip() -> str:
"""Return the machine's public IP address."""
response = requests.get("https://api.ipify.org?format=json", timeout=5)
response.raise_for_status()
return response.json()["ip"]
def main() -> None:
ip = get_public_ip()
print(f"Your public IP address is: {ip}")
if __name__ == "__main__":
main()
If you try to run this script without activating the environment (or without
installing requests), you get:
That error is the clearest sign that either the environment is not active or the package was not installed.
4.3 Cloning a Project and Recreating the Environment¶
Imagine a colleague shares their project with you. The repository contains
source code and a requirements.txt but no venv/ directory (correctly).
# 1. Clone the repository
git clone https://github.com/example/their-project.git
cd their-project
# 2. Create a fresh virtual environment
python -m venv venv
# 3. Activate it
source venv/bin/activate # macOS/Linux
# venv\Scripts\activate # Windows
# 4. Install all dependencies from the requirements file
pip install -r requirements.txt
# 5. Run the project
python main.py
Because requirements.txt pins exact versions, you get the same environment
your colleague used.
4.4 Adding a New Dependency Mid-Project¶
You are working on an existing project and decide to add a new package:
# Make sure the environment is active first
pip install httpx
# Update requirements.txt to include the new package
pip freeze > requirements.txt
# Commit the updated requirements file
git add requirements.txt
git commit -m "Add httpx dependency"
Always regenerate requirements.txt after installing or upgrading anything.
It is easy to forget, and then your requirements file drifts out of sync with
what is actually installed.
4.5 Checking What Is Installed vs. What Is Required¶
Over time, you might install packages for experimentation and forget to remove them. A quick way to audit:
# See everything currently installed
pip list
# Compare with what requirements.txt specifies
pip install -r requirements.txt --dry-run
Or install pip-check for a more detailed report:
For a clean slate, delete the environment and recreate it from
requirements.txt:
deactivate
rm -rf venv # macOS/Linux
# rmdir /s /q venv # Windows
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
This is the nuclear option, but it guarantees your environment matches your requirements file exactly.
4.6 A requirements.txt for a Real Project¶
Here is what a realistic requirements.txt might look like for a small web
scraping project:
beautifulsoup4==4.12.3
certifi==2024.2.2
charset-normalizer==3.3.2
idna==3.6
lxml==5.1.0
requests==2.31.0
soupsieve==2.5
urllib3==2.2.1
The project directly uses requests and beautifulsoup4. Everything else is
a transitive dependency that pip installed automatically. All versions are
pinned so the project is reproducible.
4.7 Multiple Environments for the Same Project¶
You might want separate environments for different purposes:
# Development environment (includes testing and linting tools)
python -m venv venv-dev
source venv-dev/bin/activate
pip install requests pytest ruff mypy
pip freeze > requirements-dev.txt
# Production environment (only runtime dependencies)
python -m venv venv-prod
source venv-prod/bin/activate
pip install requests
pip freeze > requirements.txt
This pattern keeps your production environment lean. Testing tools like
pytest and linters like ruff are only needed during development, not when
running the application.
5. Common Mistakes¶
5.1 Forgetting to Activate the Environment¶
This is the most common mistake. You create the environment, then install
packages or run your script without activating first. The packages go into
the global Python installation (or you get a ModuleNotFoundError).
# Wrong — environment exists but is not active
python -m venv venv
pip install requests # installs globally, not into venv!
python main.py # may fail if requests is not globally installed
# Right — activate first
python -m venv venv
source venv/bin/activate # (venv) appears in prompt
pip install requests # installs into venv
python main.py # works
How to check: Look at your prompt. If you see (venv) at the start, the
environment is active. If not, activate it.
You can also check which Python is being used:
If the path points inside your project's venv/ directory, you are good.
5.2 Committing venv/ to Git¶
The venv/ directory can contain thousands of files and tens of megabytes of
data. It is platform-specific (a venv/ created on macOS will not work on
Windows). And it is entirely reproducible from requirements.txt.
Committing it to git bloats your repository, causes merge conflicts, and provides no benefit.
If you accidentally committed venv/ already:
# Remove it from git tracking (but keep the directory locally)
git rm -r --cached venv/
echo "venv/" >> .gitignore
git add .gitignore
git commit -m "Remove venv from version control"
5.3 Not Pinning Versions¶
Writing requirements.txt by hand with unpinned versions is a common
shortcut that causes problems later:
Six months from now, pip install -r requirements.txt might install newer
versions with breaking changes. Your project stops working and you have no
record of what versions you were using.
Always generate requirements.txt with pip freeze:
This captures exact versions for everything, including transitive dependencies.
5.4 Installing Packages Globally Instead of in the Environment¶
If you run pip install without an active environment, packages go into your
global Python installation. This pollutes the global environment and defeats
the purpose of virtual environments.
# Wrong — no environment active
pip install requests # goes into global Python
# Right — activate first
source venv/bin/activate
pip install requests # goes into venv
A useful habit: if you ever run pip install and do not see (venv) in your
prompt, stop and activate the environment first.
5.5 Forgetting to Update requirements.txt¶
After installing a new package, it is easy to forget to regenerate
requirements.txt. Your code works locally because the package is installed,
but when someone else clones the project and runs pip install -r
requirements.txt, the package is missing.
Make it a habit: every time you run pip install, immediately follow it with:
Some teams add a reminder comment at the top of requirements.txt:
5.6 Using the Wrong Python to Create the Environment¶
If you have multiple Python versions installed, make sure you create the environment with the version your project requires.
# Check which Python you are using
python --version
python3 --version
python3.11 --version
# Create the environment with a specific version
python3.11 -m venv venv
Once the environment is created and activated, python inside it always
refers to the version it was created with, regardless of what python means
globally.
5.7 Deleting the Environment and Losing Track of Dependencies¶
If you delete venv/ before generating requirements.txt, you lose the
record of what was installed. Always generate requirements.txt before
deleting an environment.
# Before deleting, save dependencies
pip freeze > requirements.txt
git add requirements.txt
git commit -m "Save dependencies before cleanup"
# Now safe to delete
deactivate
rm -rf venv
6. Practice Tasks¶
-
Create a new directory called
practice-env. Inside it, create a virtual environment, activate it, and verify thatpythonpoints to the environment by runningwhich python(macOS/Linux) orwhere python(Windows). -
With the environment active, install the
requestspackage. Runpip listandpip show requeststo confirm it is installed. Then runpip freezeand observe the output. -
Generate a
requirements.txtfile from the active environment. Open it and identify which packages are direct dependencies (ones you installed) and which are transitive dependencies (installed automatically). -
Deactivate the environment, delete the
venv/directory, create a fresh environment, and reinstall everything fromrequirements.txt. Verify the same packages are present. -
Create a
.gitignorefile inpractice-env/that excludesvenv/,.venv/, and__pycache__/. Initialize a git repository and confirm thatgit statusdoes not show thevenv/directory. -
Write a small script
fetch_title.pythat usesrequeststo fetch the HTML ofhttps://example.comand print the content of the<title>tag. (Hint: you can find the title with simple string operations — no HTML parser needed for this exercise.) Run it from within the active environment. -
Install
pytestinto the environment. Write a test filetest_utils.pywith a simple function and a test for it. Runpytestand confirm it passes. Then regeneraterequirements.txtto includepytest. -
Create two separate environments in the same project directory:
venv-dev(withpytestandrequests) andvenv-prod(with onlyrequests). Generate separaterequirements.txtandrequirements-dev.txtfiles. Compare them.
7. Key Takeaways¶
- A virtual environment is an isolated Python installation for a single project. Always use one — never install project packages globally.
- Create an environment with
python -m venv venv. Activate it withsource venv/bin/activate(macOS/Linux) orvenv\Scripts\activate(Windows). Deactivate withdeactivate. - Install packages with
pip install <package>. Uninstall withpip uninstall <package>. pip listshows installed packages in a readable table.pip freezeshows them inname==versionformat, ready for a requirements file.- Generate
requirements.txtwithpip freeze > requirements.txt. Recreate an environment from it withpip install -r requirements.txt. - Always pin versions in
requirements.txt. Usepip freezeto generate the file — do not write it by hand. - Never commit
venv/to git. Add it to.gitignore. Commitrequirements.txtinstead. - Upgrade pip itself with
python -m pip install --upgrade pip. - The most common mistake is forgetting to activate the environment before
running
pip installor your script.
Further Reading¶
What's Next¶
Ready to continue? Head to the next chapter: Object-Oriented Programming.
→ Chapter 16 — Object-Oriented Programming
See also: - Exercise - Solution - Cheatsheet