skip to content

Installing Packages with pip

The everyday install surface: where pip fetches from, what its backtracking resolver does, and how a requirements file feeds it. Interviewers ask so they can follow up on two dependencies that disagree.

part ofPythonoverview, primer and where to startread it →
on this pageshow

questions

4

What can `pip install` take as a target besides a package name from PyPI?

level: juniorimportance: must knowfreq 68%

answer

  1. More than a name from the index
  2. The syntax decides fetch and build
  3. Path, URL, repository, or a file of lines
  4. Wheel unpacks, directory and sdist build first
  5. git+https with @commit, -r for a list

basics

~20 s

Besides a name from an index, pip install accepts a path to a project directory, wheel or sdist, a direct URL, a version-control reference such as git+https at a tag or commit, and -r to read requirement lines from a file.

solid answer

~40 s

Four target shapes cover almost everything. A **requirement specifier** (`example-lib`, `example-lib[async]>=2.4,<3`) is looked up on the configured index. A **local path** is a project directory, a `.whl` or an sdist: a wheel is just unpacked, while a directory or sdist is built into a wheel first via the backend named in its `pyproject.toml`. A **direct URL** in PEP 508 form (`example-lib @ https://host/example_lib-2.4.1-py3-none-any.whl`) bypasses the index for that one requirement. A **VCS reference** (`git+https://git.example.com/example-lib.git@8f3c1d2`) clones the repository at a ref and builds the checkout. `-r requirements.txt` is not a target itself: it is a file whose lines are targets, plus options. All of them can be mixed in one command and are resolved together.

code

console · 4 lines
console
python -m pip install "example-lib[async]>=2.4,<3"
python -m pip install "example-lib @ git+https://git.example.com/example-lib.git@8f3c1d2"
python -m pip install ./dist/example_lib-2.4.1-py3-none-any.whl
python -m pip install -r requirements.txt

go deeper

for a junior

Be ready to name the target shapes out loud: a requirement, a path to a wheel or project, a URL, a git reference, and -r for a file of requirements. Knowing that -r reads a plain text list is the part interviewers actually check.

for a middle

Explain the mechanics behind each shape: a wheel is unpacked, a directory or sdist triggers a PEP 517 build in an isolated environment, and a VCS target clones and then builds. Mention that a directory install is a copy, not a link.

for a senior

Show judgement about reproducibility: pin VCS targets to a commit, prefer built wheels in CI, and know how --no-index with --find-links makes an air-gapped install work. Say what each shape costs the people who consume your project.

for a principal

Own the policy: which target shapes are allowed in a production requirements file, whether VCS references are permitted at all and for how long, and whether builds happen once in CI or on every deployment machine.

### The argument shape tells pip where to fetch and whether to build Every positional argument to `pip install` is an *install target*. Its syntax decides two things: where the distribution comes from, and whether a build step has to happen before anything lands in `site-packages`. **1. A requirement specifier.** `example-lib`, `example-lib==2.4.1`, `example-lib>=2.4,<3`, `example-lib[async]` (an *extra*, an optional dependency group the project declares), optionally with an environment marker such as `; python_version < "3.13"`. pip asks the configured index for the files published under that project name and picks the best version satisfying the specifier. Project names are normalised the way the index requires: case is ignored and runs of `-`, `_` and `.` collapse, so `Example_Lib` and `example-lib` are the same distribution. **2. A local path.** `.`, `./libs/example-lib`, `dist/example_lib-2.4.1-py3-none-any.whl`, `dist/example_lib-2.4.1.tar.gz`. A wheel is already built: pip unzips it into the environment, writes the `.dist-info` directory with its `METADATA` and `RECORD`, and generates console-script wrappers from the entry points. A directory or an sdist is *source*: pip reads `pyproject.toml`, creates an isolated build environment, installs the build requirements declared there, calls the build backend to produce a wheel, then installs that wheel. The consequence people miss is that a directory install **copies** the built result — after it finishes, editing the source tree changes nothing in the environment. **3. A direct URL.** In PEP 508 form, `example-lib @ https://host/example_lib-2.4.1-py3-none-any.whl`, or a bare URL as the argument. For that one requirement the index is not consulted at all; the URL is recorded in the installed metadata, so a later `pip freeze` shows the URL rather than a plain version. **4. A version-control reference.** `git+https://git.example.com/[email protected]` — with `git+ssh`, and Mercurial, Subversion and Bazaar equivalents. pip shells out to the VCS client, which therefore has to be installed and on `PATH`, checks out the given branch, tag or commit, and then treats the checkout as a source tree and builds it exactly as in case 2. A fragment such as `#subdirectory=packages/example-lib` selects one project out of a monorepo. The ref is the reproducibility hinge: a branch name moves, a tag can be re-pointed, and only a full commit hash is genuinely immutable — `@main` in a requirements file means two installs a week apart can silently differ. **5. `-r requirements.txt`.** A file whose non-comment lines are targets of the shapes above, one per line, plus per-line options and file-level options like an index URL at the top; a nested `-r other.txt` pulls in another file. It is an input list, not a lock format — nothing about it is generated or validated by pip unless you produced it with `pip freeze`, and two people installing the same unpinned file on different days can get different versions. ### Fetching without an index at all `--no-index` tells pip never to contact an index, and `--find-links <dir-or-url>` points it at a directory or page of distribution files instead. That pair is how air-gapped and locked-down builds work: download wheels once on a connected machine, ship the directory, install from it. It composes with all the target shapes above. ### What actually lands in the environment Whichever route the distribution arrives by, the end state is identical: an unpacked wheel. Modules go into `site-packages`, metadata into `<name>-<version>.dist-info`, and any `console_scripts` entry points become executables in the environment's script directory. `pip show` and `pip freeze` read that metadata afterwards; they cannot tell you the target *shape* you typed except when the install came from a URL or VCS, in which case the recorded direct-URL metadata preserves it. ### Choosing between them in practice Use a plain requirement for anything published. Use a local wheel path when you already built the artifact and want the exact bytes installed — a release candidate, or a CI job that builds once and installs into several test environments. Use a VCS reference, pinned to a commit, when you need a fix that is merged upstream but not yet released, and treat it as temporary: it forces every consumer of your project to have a VCS client and network access to that host. Use a directory path for a one-off local build, remembering it is a snapshot copy rather than a live link.

  • What does pip do differently when the target is a `.whl` file rather than a project directory?
    A wheel is a built artifact: pip unzips it into `site-packages`, writes the `.dist-info` metadata and creates entry-point scripts, with no build step and no build dependencies. A directory (or an sdist) is source: pip reads `pyproject.toml`, provisions an isolated build environment with the declared build requirements, asks the backend to produce a wheel, and installs that. So a directory install needs a working build toolchain and network access for the build requirements; a wheel install needs neither.
  • Why is `git+https://git.example.com/example-lib.git@main` a poor thing to leave in a requirements file?
    `main` is a moving ref. Two installs a week apart can build different code while reporting the same project version, so a bug reproduces on one machine and not another, and the wheel cache keyed on the resolved commit will not warn you. Pin a full commit hash — a tag is better than a branch but can still be re-pointed — and treat any VCS requirement as a temporary bridge until the fix is released to an index.
  • What changes if you add `--user` to a pip install command?
    Files land in the per-user site directory (`site.USER_SITE`) instead of the active environment's `site-packages`, so they are visible to every interpreter of that version for that user rather than to one project. Inside an activated virtual environment pip refuses the flag outright, because the user site directory is not on that environment's import path. It is a workaround for a system interpreter you cannot write to, not a substitute for a project environment.

saying these in an interview costs you the question

  • Thinks pip can only install names published to an index
  • Believes installing a directory keeps it linked to the source tree
  • Assumes a git+https target works without a git client installed
  • Calls requirements.txt a lockfile that pip generates itself
  • Thinks a local project must be uploaded somewhere before installing

context

open as a page

What does `pip install --no-deps` actually do, and when is skipping dependencies right?

level: middleimportance: should knowfreq 38%

basics

~20 s

--no-deps installs only the distributions you named and ignores the requirements declared in their metadata, so pip does no resolution and installs nothing extra. It is right when something else has already produced the complete, exact set to install.

open as a page

What does pip cache locally, and what does `--no-cache-dir` trade away?

level: middleimportance: should knowfreq 33%

basics

~20 s

pip keeps a per-user cache of downloaded files and index responses, plus wheels it built from source distributions, so a repeat install skips the download and the rebuild. --no-cache-dir disables it, keeping an image layer small but repeating that work.

open as a page

How do pip's `--index-url` and `--extra-index-url` differ when a private index is in play?

level: seniorimportance: should knowfreq 44%

basics

~20 s

--index-url replaces the default index; --extra-index-url adds a second index that is searched alongside it. pip pools candidates from every configured index and picks the best version overall, so it has no notion of one index being more trusted than another.

open as a page