Installing Packages with pip

Every Python project beyond the basics eventually needs something the standard library doesn't provide — and pip install packages python is how you get it. This article covers pip and virtual environments together, since they're genuinely meant to be used as a pair, plus creating and managing a python requirements.txt file so an entire project's dependencies can be reproduced with one command.

Introduction: What pip Does and Why Virtual Environments Matter

What pip is

pip stands for "Pip Installs Packages" — it's the standard tool for installing third-party Python packages from PyPI, the Python Package Index, the central repository hosting the vast majority of publicly available Python packages.

pip install requests
Why installing globally is risky

If you install packages directly into your system's global Python installation, every project on your machine shares the exact same set of installed packages, at the exact same versions. This becomes a genuine problem the moment two different projects need different, conflicting versions of the same package — Project A needs requests version 2.25, Project B needs requests version 2.31, and a single global installation simply can't satisfy both at once.

The fix: virtual environments

A virtual environment is a self-contained directory holding its own Python interpreter and its own separately installed packages, completely isolated from both your system-wide Python installation and from any other project's virtual environment. Each project gets its own clean, independent set of dependencies, with zero risk of one project's requirements interfering with another's.

Setting Up a Virtual Environment and Basic pip Commands

Creating and activating a venv
# Create a virtual environment named 'venv' (the name is a convention, not a requirement)
python -m venv venv
# Activate it — Windows
venv\Scripts\activate

# Activate it — macOS/Linux
source venv/bin/activate

Once activated, your terminal prompt changes to show the environment's name, typically in parentheses — (venv) — confirming you're now working inside the isolated environment rather than your system's global Python. Always verify this before installing anything. It's an easy step to forget, and installing a package while believing you're inside a virtual environment, when you're actually still on the global Python, is a common, frustrating mistake to trace back later.

Installing a package
pip install requests
Installing a specific version
pip install requests==2.28.1

The == operator pins to an exact version, rather than installing whatever the latest available release happens to be — genuinely important any time you need a guaranteed, specific version, covered in more depth in Section 4.

Verifying installs
pip list
Package         Version
--------------- -------
certifi         2024.2.2
charset-normalizer 3.3.2
idna            3.6
requests        2.28.1
urllib3         2.2.1

pip list shows everything currently installed in the active environment. For detailed information about one specific package:

pip show requests
Name: requests
Version: 2.28.1
Summary: Python HTTP for Humans.
Home-page: https://requests.readthedocs.io
Requires: certifi, charset-normalizer, idna, urllib3
Required-by:

pip show is genuinely useful for quickly checking a package's installed version, its own dependencies (Requires), and what else in your environment depends on it (Required-by).

Managing Dependencies with requirements.txt

What the file is

A requirements.txt file is a plain, simple list of a project's dependencies — one package per line, optionally pinned to exact versions — letting anyone (a teammate, a deployment server, or you, on a different machine) reproduce the exact same environment with a single command.

requests==2.28.1
numpy>=1.20.0
flask
Installing from a requirements.txt
pip install -r requirements.txt

Run this after activating the appropriate virtual environment, and after navigating to the project directory containing the file (pip looks for requirements.txt in your current working directory by default, unless you point it at a different path explicitly). This single command installs every package listed in the file, at whatever version each line specifies.

Generating one from an existing environment
pip freeze > requirements.txt

pip freeze outputs every currently installed package, along with its exact installed version, and > redirects that output directly into a file.

An important caveat: pip freeze captures every package currently installed in the environment — including sub-dependencies your project didn't explicitly choose, but that got pulled in automatically because something you did choose depends on them. If you installed requests, your requirements.txt after running pip freeze will also list certifi, charset-normalizer, idna, and urllib3 — all genuine dependencies of requests itself, even though you never typed pip install certifi yourself. This is generally fine and expected, but it's worth understanding why your requirements file often ends up considerably longer than the list of packages you actually remember installing.

Version Pinning and Specifier Syntax

Exact pinning
requests==2.28.1

Exact pinning (==) guarantees a fully reproducible environment — anyone installing from this file gets precisely this version, with zero ambiguity. This is generally the safest choice for production deployments, where an unexpected version change in a dependency could introduce a bug or a breaking change you weren't prepared for.

Flexible ranges
numpy>=1.18
requests<3.0
numpy>=1.18,<2.0

Flexible version specifiers (>=, <=, <, >, or combinations of them) allow more tolerance — useful when exact reproducibility matters less than allowing minor updates and bug fixes to flow in automatically. numpy>=1.18,<2.0 says "any version from 1.18 up to, but not including, 2.0" — a common pattern for allowing patch and minor updates within a version range you've already confirmed works correctly, while still guarding against a major version bump that might introduce breaking changes.

Beyond PyPI: Git repositories and local paths

Requirements files aren't limited to packages published on PyPI — they can also reference a Git repository directly, or a local file path, for packages that aren't (or aren't yet) published:

git+https://github.com/someuser/somerepo.git@main
./local-packages/my-internal-tool

This is genuinely useful for internal, unpublished packages, or for pulling in a specific unreleased branch or commit of an open-source dependency you need a particular fix from before it's officially released.

Best Practices and Troubleshooting

Always work inside a virtual environment

Reserve global installs for a small handful of genuinely general-purpose command-line tools you want available everywhere, regardless of which project you're currently in (tools like pipx are increasingly the recommended way to handle even that specific case cleanly, keeping each global tool in its own isolated environment automatically). Never use sudo pip install as a habitual fix for a permissions error — that error is almost always a sign you're not actually inside an activated virtual environment as you intended, and forcing the install with sudo just masks the real problem while polluting your system Python further.

Splitting requirements for larger projects

For projects with meaningfully different production and development needs, it's common practice to split dependencies into two files:

# requirements.txt — what's needed to actually run the application
requests==2.28.1
flask==2.3.0
# requirements-dev.txt — additional tools only needed during development
-r requirements.txt
pytest==7.4.0
black==23.7.0
flake8==6.1.0

The -r requirements.txt line inside requirements-dev.txt includes everything from the main file too, so installing from requirements-dev.txt (pip install -r requirements-dev.txt) gives you both the production dependencies and the extra development-only tools, while a production deployment only ever needs to install from the leaner requirements.txt.

Common issues and fixes

"File not found" errors when running pip install -r requirements.txt almost always mean you're in the wrong working directory. Check with ls (macOS/Linux) or dir (Windows) to confirm requirements.txt is actually present in your current directory before troubleshooting anything more complicated.

Dependency version conflicts — pip reporting that two packages require incompatible versions of some shared dependency — usually require adjusting one specific pinned version in your requirements.txt, rather than assuming something is broadly wrong with the whole file. Read the actual conflict message carefully; pip is generally quite explicit about exactly which two requirements are in conflict and why.

Keeping the file current

Whenever you install a new package your project genuinely needs, re-run pip freeze > requirements.txt (or, if you're maintaining a hand-curated file rather than a full freeze, add the new line manually) so the file always accurately reflects your actual, real, currently-working environment. A stale requirements.txt that no longer matches what your project genuinely depends on defeats the entire purpose of having one in the first place — the whole value of the file is that anyone can trust it to reproduce a genuinely working environment, and that trust breaks the moment it falls out of sync with reality.