skip to content

Environments and Dependencies

Giving each project its own interpreter and its own packages, then getting that exact set back on another machine. Interviewers check you can make an install reproducible rather than hope pip repeats itself.

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

questions

30

What does `pip install -e .` do that a plain `pip install .` does not?

level: juniorimportance: must knowfreq 62%

answer

  1. Two installs, two very different results
  2. One copies the code, one points at it
  3. Something in site-packages redirects the import
  4. A .pth file or a generated finder
  5. PEP 660 editable wheel, no code copied

basics

~20 s

pip install -e . installs the project in editable mode: site-packages receives a pointer back to your working source tree instead of a copy of it, so a source edit takes effect on the next interpreter start without reinstalling.

solid answer

~40 s

`pip install .` builds a wheel from the project and unpacks it into `site-packages`, so the environment then owns a frozen copy of the code and every later edit needs another install to be seen. `pip install -e .` runs the same build backend but asks it for an *editable* wheel under PEP 660: what lands in `site-packages` is metadata plus a redirection — typically a `.pth` file that adds your source directory to `sys.path`, or a `.pth` line that imports a small generated finder — so `import yourpkg` loads the file you are editing. It is still a real installation: the distribution shows up in `pip list`, its dependencies are resolved and installed, and `importlib.metadata.version()` can read its version. Editable mode is a development convenience; what you deploy should be the built wheel.

code

python · 5 lines
python
import importlib.util

spec = importlib.util.find_spec("json")
print(spec.origin)
print(spec.submodule_search_locations)

go deeper

for a junior

Be ready to state the difference in one sentence: a plain install copies your code into the environment, while pip install -e . points the environment at your source tree so edits take effect without reinstalling.

for a middle

Explain the mechanics behind that sentence. The build backend still runs, but it produces an editable wheel whose payload is a redirection into your checkout plus ordinary .dist-info metadata, so dependency resolution and pip list behave exactly as normal.

for a senior

Show judgement about where each belongs. Editable in development and local test runs; a built wheel in the packaging job and in production, so a module that was never declared fails there rather than in front of a user.

for a principal

Own the policy across projects: which environments may use editable installs, where the real wheel gets exercised before release, and how you keep a developer's convenience from becoming the shape of the deployed artefact.

### Both commands go through the build backend `pip install .` and `pip install -e .` begin identically. pip reads `pyproject.toml`, looks at the `[build-system]` table, installs the declared build backend into an isolated build environment, and calls it. What differs is *which hook* pip asks for, and therefore what the resulting wheel contains. For a plain install pip asks the backend to build a normal wheel. A wheel is a zip file holding the importable package's `.py` files, any package data, and a `.dist-info` metadata directory. pip unpacks that into the environment's `site-packages`. From that moment the environment owns a private copy of your code, and the interpreter never looks at your source tree again — it looks at `site-packages`. That is exactly what a user gets when they install your project from an index, which is the value of a plain install: it exercises the real artefact. For an editable install pip asks the backend for an *editable* wheel, the hook standardised by PEP 660. The backend is free to decide how that wheel redirects imports, and the two common shapes are both tiny. The simplest writes a `.pth` file into `site-packages` whose single line is the absolute path of the directory containing your importable package; the `site` module reads every `.pth` in `site-packages` at interpreter start and appends such lines to `sys.path`, so `import yourpkg` resolves into your working tree. The stricter shape writes a `.pth` line that imports a generated module which registers a finder on `sys.meta_path`, mapping only the package names the project actually declares to their files on disk. Either way, the module source is never copied into the environment. ### An editable install is still an install This is the part candidates most often get wrong. An editable install is not a hack outside the packaging system. pip writes a normal `.dist-info` directory with `METADATA` and `RECORD`; the distribution appears in `pip list` and can be uninstalled; its declared dependencies are resolved and installed exactly as for any other install; another distribution that requires it is satisfied; `importlib.metadata.version("your-dist-name")` returns its version; and any console scripts it declares are generated into the environment's script directory. Because the install came from a local path, pip also records a `direct_url.json` in the `.dist-info` whose `dir_info` object carries `"editable": true`, which is how tooling can tell an editable install apart after the fact. ### The distribution and the importable package are different names Worth keeping straight while answering. The *distribution* is what pip installs and what `pip list` shows — the `name` in `[project]`. The *importable package* is the directory with `__init__.py` that `import` finds. They routinely differ in spelling, and an editable install is precisely the link between them: it teaches the environment that this distribution's importable package lives over there in your checkout. ### What "no reinstall needed" does not cover Only the module source is redirected. Metadata is snapshotted when you install, so adding a dependency, bumping the version, or declaring a new console script all need the install re-run. A compiled extension module still has to be rebuilt — editing its C source changes nothing until it is compiled. And a *running* interpreter caches imported modules in `sys.modules`; your edit is picked up by the next process, not by the one already executing. Development servers create the illusion of live editing by restarting the process for you. ### When to use which Use `-e` in a development environment and while running the test suite locally, where the whole point is a fast edit-run loop and where you want the tests to import the code the same way a user would. Use a built wheel everywhere the artefact matters: in a container image, in the packaging job of a pipeline, and in production. An editable install ties the environment to a working tree that may be dirty, may move, or may not exist on the target machine, and it hides a classic packaging bug — a module that imports fine because the whole source directory is on `sys.path`, but was never declared and so is missing from the real wheel. Building the wheel once and installing it is what surfaces that before a user does.

  • Does an editable install still record the distribution's metadata and dependencies?
    Yes. pip writes a normal `.dist-info` directory with `METADATA` and `RECORD`, and for a local path install a `direct_url.json` whose `dir_info` marks the install editable. Dependencies are resolved and installed as usual, `importlib.metadata.version()` works, console scripts are generated, and another distribution that depends on this one is satisfied. Only the module files are redirected instead of copied.
  • When would you deliberately install the built wheel instead of using `-e`?
    Whenever the artefact itself matters: container images, the packaging job in a pipeline, and production. An editable install makes the environment depend on a working tree that may be dirty, moved or absent, and it can hide a module that is importable only because the source directory sits on `sys.path` and was never declared for the wheel. Installing the wheel makes that fail where you can see it.
  • You edited a module, but a long-running process still uses the old code. Why?
    Because an editable install redirects where imports *resolve*, not when they happen. A module already imported is cached in `sys.modules` and is not re-read; the edit takes effect the next time the process starts. Reloading is possible but does not rebind names that other modules already imported, which is why development servers restart the process instead.

A plain install files a photocopy of your project in the library; an editable install files a slip of paper saying the original is on that desk over there.

saying these in an interview costs you the question

  • Says pip watches the filesystem and copies changed files
  • Thinks -e skips the build backend entirely
  • Believes an editable install is not a real install
  • Claims an editable install cannot satisfy another package's dependency
  • Recommends pip install -e for production deployments
  • Expects edits to reach an already-running interpreter

context

open as a page

Why can `python -m pip install` and a bare `pip install` target different interpreters?

level: juniorimportance: must knowfreq 62%

basics

~20 s

A bare pip is a script on PATH whose shebang points at whichever interpreter installed it, so PATH order decides where the install lands. python -m pip runs the pip belonging to the interpreter you just named, so the packages land where that interpreter imports from.

open as a page

Why does pyproject.toml declare dependency ranges while a deployed requirements.txt pins exact versions?

level: juniorimportance: must knowfreq 68%

basics

~10 s

pyproject.toml declares abstract dependencies: the ranges a package can work with, re-resolved at install time. A pinned requirements.txt records one concrete resolution so a particular deployment installs exactly the same versions every time.

open as a page

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

level: juniorimportance: must knowfreq 68%

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.

open as a page

What does an all-in-one Python project manager add on top of pip and venv?

level: juniorimportance: must knowfreq 58%

basics

~20 s

It owns the project end to end: it creates and syncs a project-local virtual environment, resolves the declared dependencies into a lock file, and runs commands inside that environment. pip and venv each do one piece and leave the wiring to you.

open as a page

What does activating a Python virtual environment actually change in your shell?

level: juniorimportance: must knowfreq 75%

basics

~20 s

Activation is shell bookkeeping only. Sourcing the activate script prepends the environment's bin directory to PATH, exports VIRTUAL_ENV, unsets PYTHONHOME and marks the prompt. Calling .venv/bin/python directly uses the same environment with no activation at all.

open as a page

Why is the output of `pip freeze` not a lockfile?

level: middleimportance: must knowfreq 57%

basics

~20 s

pip freeze prints a flat list of whatever is installed in one environment: names and exact versions only. It carries no artefact hashes, no environment markers and no dependency graph, so it cannot reproduce a resolution elsewhere.

open as a page

What does the `[project]` table in `pyproject.toml` (PEP 621) standardize?

level: middleimportance: must knowfreq 52%

basics

~20 s

PEP 621 defines a tool-agnostic project table in pyproject.toml holding a project's core metadata: name, version, description, requires-python, abstract dependencies, extras and entry points. Every build backend and project manager reads that same table instead of its own bespoke format.

open as a page

Why does a pip install sometimes spend minutes backtracking, and how do you cut that short?

level: middleimportance: must knowfreq 55%

basics

~20 s

pip's resolver tries the newest candidate for each requirement and, on a conflict, backs up to older ones. Every candidate costs a metadata fetch, and a source distribution costs a build, so a wide unbounded search runs for minutes. Narrow the ranges.

open as a page

Why can a single Python virtual environment hold only one version of a given distribution?

level: juniorimportance: should knowfreq 40%

basics

~20 s

Installed code lands in one flat site-packages directory, and an import binds a name to exactly one module, so two versions of the same distribution cannot coexist. An installer must therefore find one version that satisfies every requirement at once.

open as a page

After `pip install -e .`, which kinds of project change still require re-running the install?

level: middleimportance: should knowfreq 33%

basics

~20 s

Anything that is not module source read at import time: project metadata such as the version, dependencies and console-script entry points, compiled extension modules, and — under a strict editable mapping — newly added modules. Editing existing Python source needs nothing.

open as a page

What does an editable install (`pip install -e .`) actually put into site-packages?

level: middleimportance: should knowfreq 44%

basics

~20 s

Metadata plus a redirection, not your code: a normal .dist-info directory, and a .pth file whose line either adds your source directory to sys.path or imports a small generated finder that maps declared packages to their files.

open as a page

How does a Unix shebang line decide which Python interpreter runs a script?

level: middleimportance: should knowfreq 36%

basics

~20 s

When an executable file starts with #!, the kernel runs the interpreter named on that line and passes the script as an argument. #!/usr/bin/python3 pins one absolute interpreter; #!/usr/bin/env python3 defers the choice to a PATH lookup.

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

When do PEP 735 dependency groups replace optional-dependencies extras in a project?

level: middleimportance: should knowfreq 28%

basics

~20 s

Use extras for optional features your consumers install; use PEP 735 dependency groups for requirements only the repository needs, such as test, lint and docs tooling. Groups are local and never appear in the published distribution's metadata, so they cannot be installed by a consumer.

open as a page

How do you read pip's ResolutionImpossible report to find which requirements actually conflict?

level: middleimportance: should knowfreq 45%

basics

~20 s

Read the block headed "The conflict is caused by": each line names a requester and the range it demands. Lines saying the user requested something are your own direct requirements; the rest are transitive. Those ranges share no version.

open as a page

How does .venv/bin/python know it is running inside a virtual environment?

level: middleimportance: should knowfreq 55%

basics

~20 s

At startup the interpreter resolves the path of its own executable and looks for a pyvenv.cfg file beside its directory. Finding one, it sets sys.prefix to the environment, records the original installation in sys.base_prefix, and uses the environment's site-packages.

open as a page

`pip install -e .` is in place, yet an email-digest service still imports an old clock-skew fix — how do you find which copy Python is loading?

level: seniorimportance: should knowfreq 41%

basics

~10 s

Stop guessing and ask the import system: print the module's file or importlib.util.find_spec(name).origin in the exact interpreter that runs the code, then compare it against sys.path order and sys.executable to see which copy wins.

open as a page

Why does pip refuse to install into a system Python with an externally-managed-environment error?

level: seniorimportance: should knowfreq 34%

basics

~20 s

That interpreter is managed by the operating system's own package manager, which marks it with a file declaring it externally managed. pip honours the marker and refuses to modify it. The fix is to install into a virtual environment you own, not to force the install.

open as a page

What does pip's `--require-hashes` mode enforce when installing a requirements file?

level: seniorimportance: should knowfreq 33%

basics

~20 s

It makes pip reject any downloaded artefact whose hash is not listed in the requirements file. It also forces every requirement, transitive ones included, to be pinned with == and carry at least one hash, so nothing unlisted can install.

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

A project manager locks your ticket-triage bot's dependencies, but the deploy image installs with pip — what does that cost?

level: seniorimportance: should knowfreq 36%

basics

~20 s

You now have two install paths and only one source of truth. The manager's lock file is its own format that pip cannot read, so the deployment image installs from an exported file that can silently fall behind — meaning tests and production run different dependency sets.

open as a page

Two of your service's dependencies demand incompatible versions of one shared library — how do you resolve it?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Work from cheapest and most reversible outwards: check whether the capping requirement is stale and a newer release of that dependency widens it, loosen your own pin, then consider dropping the dependency, vendoring it, or splitting the two consumers into separate processes.

open as a page

An invoice-PDF renderer's .venv was copied from a laptop into the release bundle and the service dies at startup. Why does copying a venv break it, and what should ship instead?

level: seniorimportance: should knowfreq 45%

basics

~20 s

A virtual environment is full of absolute paths pinned to the machine that built it: the home key in pyvenv.cfg, console-script shebangs, and the interpreter symlink. Ship pinned requirements and build the environment on the target instead.

open as a page

What pinning policy do you set after a transitive dependency floats to a new release and breaks a video-metadata extractor's 27-minute suite with floating-point rounding drift?

level: principalimportance: should knowfreq 38%

basics

~20 s

Separate intent from resolution: keep ranges in project metadata, commit a resolved lock for every deployable, install from the lock rather than re-resolving, and re-resolve on a schedule so upgrades arrive as a reviewed change.

open as a page

What decides whether one all-in-one project manager should own every Python repository?

level: principalimportance: should knowfreq 30%

basics

~20 s

Standardize when your repositories are similar enough that one workflow fits them, and when the exit is cheap. Judge it on repository heterogeneity, CI install cost, contributor onboarding, index and authentication support, and how much of the setup survives swapping the tool out.

open as a page

How does the Windows `py` launcher decide which installed Python version to run?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

py is a single command that dispatches to one of several Python installations registered on the machine. It obeys an explicit version argument such as py -3.13, otherwise a script's shebang line, an active virtual environment, or its configured default — normally the newest installed Python 3.

open as a page

What does a constraints file passed to pip with `-c` do that a requirements file with `-r` does not?

level: middleimportance: nice to knowfreq 20%

basics

~20 s

A constraints file only bounds versions: it says that if a distribution ends up being installed, it must satisfy this specifier. It never causes an installation. A requirements file passed with -r also decides what gets installed.

open as a page

When is `python -m venv --system-site-packages` the right call, and what does it cost?

level: middleimportance: nice to knowfreq 18%

basics

~20 s

It writes include-system-site-packages = true into pyvenv.cfg, appending the base installation's site-packages to sys.path after the environment's own. Use it to reuse a binary package the base interpreter already has; it costs you reproducibility and clean isolation.

open as a page