data codes through eyeglasses

Using pip: A Practical Guide to Python’s Package Manager

pip is Python’s package installer – the tool that takes the sprawling ecosystem of 500,000+ packages on PyPI and makes them available to your project with a single command. If you’ve been using Python for more than a week, you’ve almost certainly used pip. But most developers use it through habit rather than understanding: pip install something and hope for the best. This guide covers pip properly – how it works, what you should know about managing dependencies, and the patterns that keep projects clean over time.


The Basics

The core pip commands are simple and follow a consistent pattern:

# Install a package
pip install requests

# Install a specific version
pip install requests==2.31.0

# Install a minimum version
pip install "requests>=2.28.0"

# Uninstall a package
pip uninstall requests

# List installed packages
pip list

# Show details about a specific package (version, location, dependencies)
pip show requests

# Search for packages (note: PyPI search; use https://pypi.org for richer results)
pip index versions requests

One thing worth understanding early: always run pip inside a virtual environment. If you’re not sure what that means, read the venv guide first. Installing packages globally – into your system Python – leads to dependency conflicts between projects and makes it very hard to reproduce your environment elsewhere. Virtual environments give pip a clean, isolated space to work in.


requirements.txt

The standard way to record a project’s dependencies is requirements.txt. You generate it from your active environment:

# Freeze current environment to requirements.txt
pip freeze > requirements.txt

# Install from requirements.txt
pip install -r requirements.txt

pip freeze outputs every installed package with its exact version pinned. This is what you commit to version control – it’s the reproducible snapshot of your environment.

It’s worth knowing the difference between direct dependencies (packages your code imports directly) and transitive dependencies (packages that your packages depend on). pip freeze includes both, which means requirements.txt contains more entries than you installed manually. This is correct and intentional – it ensures anyone installing your requirements gets exactly the same version of every package in the chain, not just the top-level ones.


Splitting requirements files

On real projects, it’s common to split requirements into multiple files to avoid installing development tools in production:

# requirements.txt - production dependencies
django==5.0.4
psycopg2-binary==2.9.9
gunicorn==22.0.0

# requirements-dev.txt - development and testing tools
-r requirements.txt
pytest==8.1.1
black==24.3.0
ruff==0.3.5
ipython==8.22.2

The -r requirements.txt line in the dev file includes the production requirements first. Install production dependencies with pip install -r requirements.txt and development dependencies with pip install -r requirements-dev.txt.


Upgrading Packages

pip doesn’t upgrade packages automatically when you run pip install something-already-installed. You need to be explicit:

# Upgrade a specific package to the latest version
pip install --upgrade requests

# Upgrade pip itself (do this regularly)
pip install --upgrade pip

# Check which packages have newer versions available
pip list --outdated

pip list --outdated is genuinely useful – it shows you every installed package with an available update, which packages have security patches or feature improvements you haven’t pulled in yet. Run it periodically and upgrade packages with care – major version upgrades often have breaking changes.


Installing in Editable Mode

If you’re developing a Python package (rather than using someone else’s), editable installs let you make changes to the source code and have them reflected immediately without reinstalling:

# Install the current directory as an editable package
pip install -e .

# Install a package from a local path in editable mode
pip install -e /path/to/local/package

This is standard practice when working on a package that other projects in your environment depend on – you edit the source, and any project using that package sees the changes without a reinstall cycle.


pip in Automation and CI

In CI pipelines and Docker builds, pip behaviour matters for reliability and build speed. A few useful flags:

# Don't prompt, don't show progress bar (good for CI logs)
pip install -r requirements.txt --quiet

# Don't use cache (useful if you suspect a corrupt cache)
pip install --no-cache-dir -r requirements.txt

# Install without checking package hash (rarely needed, usually avoid)
# pip install --no-deps is more commonly needed when resolving conflicts manually

In a Dockerfile, the typical pattern is:

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

The --no-cache-dir flag prevents pip from storing the download cache inside the image layer, keeping the image size down.


Faster pip with uv

If you’re doing a lot of Python work and pip’s installation speed feels slow, uv is worth knowing about. It’s a drop-in replacement for pip written in Rust that installs packages significantly faster – often 10-100x faster than pip on cold installs, with a built-in resolver that handles dependency conflicts more reliably.

# Install uv
pip install uv

# Use uv as a pip replacement
uv pip install requests
uv pip install -r requirements.txt
uv pip freeze > requirements.txt

uv also handles virtual environment creation (uv venv). The command-line interface is intentionally compatible with pip, so switching is mostly a matter of changing the command prefix. For projects where fast, reproducible installs matter – CI pipelines, Docker builds, developer onboarding – uv is worth adopting.


pip is one of those tools that’s worth understanding properly rather than just using from memory. The gap between pip install something and a well-managed dependency workflow – requirements files, split dev/prod dependencies, editable installs, regular --outdated checks – is the difference between a project that’s reproducible and a project that works until it mysteriously doesn’t.


Leave a Reply