Python’s package ecosystem is its superpower, and pip is the gatekeeper.

import requests
print(requests.__version__)

To get requests if you don’t have it:

python3 -m pip install requests

This command tells Python (using python3 -m) to run the pip module and execute its install command, fetching the requests package from the Python Package Index (PyPI) and placing it in your current Python environment’s site-packages directory.

Managing packages is about more than just installing. You can list what’s installed:

python3 -m pip list

This will output a table of installed packages and their versions. To see just the packages and their versions in a format suitable for re-installation:

python3 -m pip freeze

This is crucial for reproducibility. If you have a project with specific dependencies, you can capture them:

python3 -m pip freeze > requirements.txt

Later, on another machine or in a fresh virtual environment, you can install all those exact versions:

python3 -m pip install -r requirements.txt

This requirements.txt file is the bedrock of reproducible Python development.

Virtual environments are the standard way to isolate project dependencies. If you don’t use them, pip install will put packages into your global Python installation, leading to version conflicts when different projects need different versions of the same library.

To create a virtual environment:

python3 -m venv myproject_env

This creates a myproject_env directory containing a copy of the Python interpreter and a clean site-packages directory. To activate it:

  • On macOS/Linux:
    source myproject_env/bin/activate
    
  • On Windows (Command Prompt):
    myproject_env\Scripts\activate.bat
    
  • On Windows (PowerShell):
    myproject_env\Scripts\Activate.ps1
    

Once activated, your shell prompt will change, indicating you’re in the virtual environment. Now, pip install commands will only affect this isolated environment.

(myproject_env) $ python -m pip list

The output will be minimal, showing only pip and setuptools.

To uninstall a package:

(myproject_env) $ python -m pip uninstall requests

pip will ask for confirmation.

The real magic of pip is its ability to resolve dependencies. When you install requests, it might also pull in charset-normalizer, idna, urllib3, and certifi because requests depends on them. pip figures this out by inspecting the metadata of the requested package.

When you execute python3 -m pip install some-package, pip first queries the Python Package Index (PyPI) for some-package. It retrieves its metadata, which includes a list of other packages it requires, along with their version constraints. pip then recursively queries for these dependencies, ensuring that compatible versions of all required packages are installed. It builds a dependency graph and then installs the packages in an order that satisfies all constraints, downloading them from PyPI and placing them into the site-packages directory of the active Python environment.

If you need a specific version, you can specify it:

(myproject_env) $ python -m pip install "django==4.1.7"

Or a minimum version:

(myproject_env) $ python -m pip install "django>=4.0"

Or a range:

(myproject_env) $ python -m pip install "django>=4.0,<5.0"

The requirements.txt file is just a plain text file. The format is package_name[==|>=|<=|~=]version. The ~= operator, for example, allows for compatible upgrades within a minor version. ~=4.1.7 is equivalent to >=4.1.7,<4.2.0. This fine-grained control is essential for managing complex projects.

You can also upgrade a package:

(myproject_env) $ python -m pip install --upgrade requests

This tells pip to find the latest compatible version of requests and install it, updating the existing one if necessary.

The pip command itself can sometimes be updated. If you encounter strange errors with pip, try upgrading it:

python3 -m pip install --upgrade pip

This ensures you’re using the latest bug fixes and features of the package installer itself.

When pip install runs, it downloads wheels (pre-compiled binary distributions) or source distributions (sdist) from PyPI. For wheels, it’s a simple extraction into site-packages. For sdists, it might involve compiling code, which requires build tools like C compilers to be present on your system. This is why installing packages like numpy or pandas can sometimes fail on a clean system if the necessary build dependencies aren’t installed first.

The ability to pin exact versions in requirements.txt is a double-edged sword. While it guarantees reproducibility, it can also lead to "dependency hell" if a critical dependency is no longer maintained or has security vulnerabilities. Regularly reviewing and updating your dependencies, perhaps using tools like pip-audit or dependabot, is a crucial part of maintaining a healthy project.

The next hurdle is often managing different Python versions alongside different package environments, which is where tools like pyenv or conda come into play.

Want structured learning?

Take the full Pip course →