skip to content

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

level: middleimportance: should knowfreq 44%

answer

  1. Not the code — something stands in
  2. One metadata directory, one text file
  3. The site module reads it at startup
  4. Two line forms: a path, or import
  5. A .pth path entry or generated finder

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.

solid answer

~50 s

Two things. First a normal `.dist-info` directory with `METADATA`, `RECORD` and a `direct_url.json` marking the install editable — that is what makes it a real installation. Second, in place of the module files, a `.pth` file. The `site` module reads every `.pth` in `site-packages` at interpreter start, appends each line that looks like a path to `sys.path`, and *executes* any line beginning with `import`. Backends use both forms: the permissive mode writes the absolute path of the directory holding your package, so that whole directory becomes importable; the strict mode writes an `import` line that installs a generated finder onto `sys.meta_path` which maps only the declared package names to their files. The permissive form is why a flat layout can make unrelated files in the project root importable, and why a src layout keeps the redirection narrow.

code

python · 8 lines
python
import glob
import os
import site

for directory in site.getsitepackages():
    for pth in sorted(glob.glob(os.path.join(directory, "*.pth"))):
        with open(pth, encoding="utf-8") as handle:
            print(os.path.basename(pth), "->", handle.read().strip()[:70])

go deeper

for a junior

Know that nothing of your code is copied into the environment: site-packages gets a small text file that tells the interpreter where your source lives, plus the usual metadata directory.

for a middle

Be able to name the two shapes and the rule behind them: the site module adds path lines in a .pth to sys.path and executes lines starting with import, which is how a backend can register a finder on sys.meta_path.

for a senior

Reason about the consequences. A path-style redirection widens the importable surface to a whole directory, so local imports can succeed for modules that never make it into the wheel; a strict mapping avoids that but goes stale when you add files.

for a principal

Decide how much the development import surface is allowed to differ from the shipped one, and where that difference gets caught — a pipeline stage that installs the built wheel and imports it is what keeps the two honest across many repositories.

### The two halves of what gets installed An editable install writes metadata like any other install, and a redirection instead of the code. The metadata half is unremarkable and important: a `.dist-info` directory named for the distribution and its version, containing `METADATA` (name, version, requirements), `RECORD` (the files this install owns, so it can be uninstalled), and — because the install came from a local directory — a `direct_url.json` written per PEP 610 whose `dir_info` object contains `"editable": true`. This is what makes the distribution visible to `pip list`, uninstallable, able to satisfy someone else's requirement, and readable through `importlib.metadata`. The redirection half is the interesting one, and PEP 660 deliberately leaves its shape to the build backend. In practice you will see one of two things in `site-packages`. ### Form one: a path in a .pth file A `.pth` file is a plain text file in a site directory. At interpreter start the `site` module scans each site directory for `*.pth`, and for every line it either adds the line to `sys.path` when it names a directory, or, when the line starts with `import`, executes it. That second rule is old, obscure, and exactly what modern editable installs lean on. The permissive form uses the first rule: the file contains one absolute path, the directory that holds your importable package. After that, everything in that directory is importable, not just your package. In a flat layout — where the project root holds both `yourpkg/` and `tests/`, `docs/`, `conftest.py`, `build/` — that means the *whole project root* joins `sys.path`. A stray `tests` or `utils` directory in the root becomes a top-level importable name in the environment, which can shadow a real distribution or accidentally import fine locally while being absent from the built wheel. With a src layout the mapped directory is `src/`, which contains only the packages you ship, so the redirection is naturally narrow. That interaction is the practical reason many projects reach for a src layout, though the layout choice itself is a separate topic. ### Form two: an import hook in a .pth file The strict form uses the execute rule. The `.pth` line reads something like an `import` of a generated module that the same wheel dropped next to it; importing that module registers a finder object on `sys.meta_path`. The finder holds an explicit mapping from declared module and package names to absolute file paths in your checkout, and it answers only for those names. Nothing extra becomes importable, so the local import surface matches the wheel's much more closely. The cost is that the mapping is a snapshot: a module file you add after installing is not in the mapping and will not import until you reinstall. You can see the machinery from a REPL. Listing `*.pth` in the directories reported by `site.getsitepackages()` shows exactly which redirection a project got, and printing the class names in `sys.meta_path` shows whether a generated finder was installed ahead of the ordinary path-based one. ### Historical form: the egg-link Before PEP 660, `pip install -e` ran `setup.py develop`, which wrote an `.egg-link` file naming the project directory plus an entry appended to a shared `easy-install.pth`. The effect on `sys.path` was similar, but the file was not a wheel install, uninstallation and metadata were shakier, and it required a `setup.py`. You may still meet `.egg-link` files in old environments; it is worth recognising them as the ancestor of today's `.pth`. ### Why this matters in an interview Because it converts a piece of magic into a file you can read. If imports are not picking up your edits, the fastest question is: what is actually in `site-packages` for this distribution? A `.pth` pointing at the wrong checkout, an old copy of the package sitting next to it as real files, a `.dist-info` without `"editable": true`, or a strict mapping that predates the module you just added — each is visible in seconds, and each explains a different failure. Candidates who can name the `.pth` mechanism and the `site` module's two rules can debug an editable install; candidates who cannot are left restarting things and hoping.

  • How does the `site` module decide what to do with a line inside a `.pth` file?
    At interpreter start it reads each `*.pth` in every site directory. Blank lines and lines starting with `#` are skipped; a line beginning with `import` is executed as Python code; anything else is treated as a path and, if the directory exists, appended to `sys.path`. The execute rule is what lets a backend install a custom finder from a text file.
  • Why can a permissive editable install make files you never packaged importable?
    Because it puts a whole directory on `sys.path`. In a flat layout that directory is the project root, so siblings of your package — `tests`, `scripts`, a stray `utils` — become top-level importable names in the environment. Code can then import something that is absent from the built wheel, and the failure only appears after a real install. A src layout narrows the mapped directory to what you ship.
  • How can you tell after the fact that a distribution was installed editable?
    Look in its `.dist-info` for `direct_url.json`: an editable path install records the source directory as the URL and `"editable": true` inside `dir_info`, per PEP 610. `pip list` can also filter to editable installs, and the absence of the package's own `.py` files from `RECORD` is a second clue.

The .pth file is a forwarding address left with the post office: the interpreter finds a card in site-packages that says where the real code lives, rather than the code itself.

saying these in an interview costs you the question

  • Says the package files are copied and then symlinked back
  • Cannot name what a .pth file does at startup
  • Thinks no metadata directory is written for an editable install
  • Believes a .pth file is imported like a module
  • Assumes every editable install uses the same mechanism
  • Ignores that a path entry exposes the whole directory

context