skip to content

`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%

answer

  1. The interpreter already knows the answer
  2. Ask it in the process that fails
  3. Two things: which file, which interpreter
  4. Read sys.path top-down, first match wins
  5. find_spec origin, __file__, sys.executable

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.

solid answer

~50 s

Make the interpreter tell you where the module came from rather than reasoning about it. In the same process that misbehaves, print `sys.executable`, then `importlib.util.find_spec("yourpkg").origin` and the module's `__file__` after import. Almost always the origin is not your checkout, and the reason is one of four: a leftover non-editable install of the same distribution sitting in `site-packages` as real files; a different environment than the one you installed into; a directory earlier on `sys.path` — the script's directory, the current directory for `-c` and the REPL, or a `PYTHONPATH` entry — that contains a copy or an old build output; or an already-imported module cached in `sys.modules` from before the edit. Fix the shadowing rather than layering another install on top: uninstall the stale distribution until `pip` reports it gone, remove stray build output, and re-run the editable install.

code

python · 9 lines
python
import importlib.util
import sys

name = "json"  # replace with the package you are debugging
spec = importlib.util.find_spec(name)
print("interpreter:", sys.executable)
print("resolves to:", spec.origin)
for entry in sys.path:
    print("path:", entry)

go deeper

for a junior

Learn the one-line reflex: print the module's __file__ after importing it. Knowing which file actually loaded turns a confusing bug into an obvious one.

for a middle

Explain the resolution order — sys.path is searched top to bottom and the first match wins, with the script or current directory ahead of site-packages — and know that an already-imported module is cached in sys.modules for the process's life.

for a senior

Demonstrate the full diagnosis under time pressure: interpreter, resolved origin, path order, what the installed distribution claims to own, then remove the shadow at its source rather than adding another path entry on top.

for a principal

Make the class of failure structurally unlikely: a layout where the working directory holds nothing importable, a test bootstrap that reports the resolved origin on failure, and a rule that a pipeline installs the built artefact so a developer's path accident never becomes a release.

### The failure looks like magic and is not An eleven-person team ships an email-digest service. Someone fixes a clock-skew bug in the scheduling module — timestamps a few seconds ahead of the sender's clock were rounding a digest into the previous window — and the fix works when they run the module directly, but the test suite and one colleague's checkout keep producing the old behaviour, even though everyone ran `pip install -e .`. Nothing here is mysterious: some other file is being imported, and Python will tell you which one. ### Step one: ask the import system, in the failing process The only authoritative answer comes from the process that actually misbehaves, so put the probe there — at the top of the failing test, or in the entry point: ```python import sys, importlib.util print(sys.executable) print(importlib.util.find_spec("digestlib").origin) ``` `find_spec` runs the real resolution without importing, and `origin` is the file that would be loaded. After an import, `digestlib.__file__` says the same thing for the module already in memory — and comparing the two is itself informative, because they differ when the module was imported before something mutated `sys.path`. ### Step two: name the shadow Four causes cover nearly every occurrence. **A leftover regular install.** The distribution was once installed normally — from an index, from a wheel, by a bootstrap script — and those real `.py` files still sit in `site-packages`. An editable install adds a redirection but does not delete the older copy's files if that copy was recorded by a different install path, and directories in `site-packages` are found by the ordinary path-based finder. Uninstalling the distribution repeatedly until pip reports nothing to remove, then reinstalling with `-e`, is the cure. **The wrong environment.** The interpreter running the tests is not the one that received the editable install. `sys.executable` and `sys.prefix` settle it in one line. **A directory earlier on `sys.path`.** `sys.path[0]` is the script's directory for `python script.py`, the current directory for `python -c` and the REPL, and the current directory for `python -m`. If a stale copy of the package — a `build/` tree, a vendored snapshot, an old checkout — lives there, it wins over anything the editable redirection contributes. `PYTHONPATH` entries also precede `site-packages`. Printing `sys.path` in order, and reading it top-down, is the whole diagnosis. Since 3.11, `PYTHONSAFEPATH` removes that leading directory entry, which makes an excellent experiment: if the failure disappears with it set, the shadow was local. **Nothing stale at all — just a cached module.** A module imported before your edit stays in `sys.modules` for the life of the process. Long-lived workers, notebook kernels and watch-mode runners all reproduce this. Restart the process. ### Step three: confirm what the install owns `importlib.metadata.files("your-dist-name")` lists the files the installed distribution claims, and `importlib.metadata.version()` confirms which distribution answered. If the listing contains the package's own `.py` files, this is a regular install pretending to be your source; if it contains only metadata and a `.pth`, the editable redirection is intact and the shadow is coming from `sys.path` instead. ### Why a src layout makes this rarer With the importable package under `src/`, the current directory contains nothing importable. `import digestlib` cannot silently resolve to the working tree by accident; it resolves only through the install, exactly as it will for a user. The failure mode inverts from "quietly imported the wrong copy" to "ImportError until you install", which is far cheaper to diagnose. In a flat layout, the project root doubles as an implicit path entry for anything launched from it, and that is what lets an uninstalled or shadowed package appear to work — and what lets a stale copy elsewhere take over when it does not. ### The habit worth demonstrating An interviewer is listening for whether you reach for evidence or for ritual. Reinstalling, clearing caches and deleting `__pycache__` are ritual. Printing `sys.executable`, the resolved origin and `sys.path` — in the failing process, in that order — is evidence, and it takes about fifteen seconds. Add it to the team's test bootstrap once and the whole class of report disappears, because the wrong path is visible in the failure output rather than in someone's guess.

  • The resolved origin points at site-packages even though you installed with `-e`. What happened?
    An older regular install of the same distribution left real `.py` files in `site-packages`, and the ordinary path-based finder finds that directory before anything the editable redirection adds. Uninstall the distribution until pip reports nothing left to remove, check that no orphaned package directory remains beside the `.dist-info`, then reinstall editable and re-check the origin.
  • How do you prove the current directory, not the install, is what makes an import succeed?
    Run the same import with the leading path entry suppressed — set `PYTHONSAFEPATH` for the run, available since 3.11 — or run from an unrelated working directory. If the import then fails, the package was resolving through the current directory rather than the installed redirection, which means the install is broken or was never done in that environment.
  • Why does deleting `__pycache__` almost never fix this?
    Cached bytecode is validated against the source file's metadata, so a stale `.pyc` is invalidated automatically when the `.py` changes. If you are running old code, you are running a different *file*, not old bytecode from the right one. Deleting caches wastes a compile and hides the real question, which is which path the import resolved through.

Two folders with the same label sit in the same drawer; the clerk always takes the first one they touch. You do not reorganise the drawer until you know which folder the clerk is picking up.

saying these in an interview costs you the question

  • Reinstalls repeatedly instead of printing the resolved path
  • Blames stale __pycache__ bytecode for old behaviour
  • Never checks which interpreter is running the code
  • Assumes an editable install always wins over site-packages
  • Adds the source directory to PYTHONPATH to paper over it
  • Ignores that sys.path order decides the first match

context