Why does a local file named logging.py shadow the standard library's logging module?
answer
- The first match on the search path wins
- One entry is prepended before your code runs
- Which entry depends on how you launched
- Rename the file, or stop prepending
- -P, PYTHONSAFEPATH, sys.flags.safe_path
basics
~10 sThe interpreter prepends the running script's own directory to sys.path, so import searches it before the standard library and takes the first match. Run with -P, or set PYTHONSAFEPATH=1, to drop that prepended entry.
solid answer
~40 sImport walks `sys.path` in order and stops at the first entry that can supply the name; the standard library has no privileged position in that list. At startup the interpreter prepends one entry for you: the directory containing the script for `python app.py`, the working directory for `python -m pkg.mod`, and an empty string meaning the working directory for `python -c` and the REPL. A sibling `logging.py` therefore wins, and once it is bound every later `import logging` in the process gets it from `sys.modules`. The real fix is to rename the file. The interpreter-level fix is `-P` or `PYTHONSAFEPATH=1` (Python 3.11+), which suppress that prepended entry entirely; `sys.flags.safe_path` reports it, and `-I` implies the same behaviour.
code
python · 4 linesimport sys
print(sys.path[0])
print(sys.flags.safe_path)go deeper
Recall that import takes the first match on sys.path, and that your script's own directory is put at the front. Recognise the symptom: a module that imports fine but is missing the attribute you expected.
Be ready to say which entry is prepended for python app.py, python -m pkg and python -c, and to name -P or PYTHONSAFEPATH=1 as the interpreter-level suppression, with renaming the file as the real fix.
Show how you would diagnose this in a running system - printing a module's origin and sys.path[0] - and explain why sys.modules caching means the fix is a restart, and why child processes need the flag passed to them explicitly.
Own the policy: whether teams may rely on the working directory being importable at all, or whether every entry point is installed and launched with safe path on, so that no machine's layout can change which code runs.
### The import system has no favourites `import logging` does not mean "load the standard library's logging package". It means "find the first thing on `sys.path` that can supply a top-level module named `logging`". The import machinery walks `sys.path` in order, asks each entry's finder whether it can provide that name, and stops at the first yes. The standard library directory sits somewhere in the middle of that list, so anything ahead of it wins outright. What sits ahead of it is the entry the interpreter prepends for you before your first line runs. Which entry that is depends on how the process was launched: - `python app.py` - the directory containing `app.py` (not necessarily the working directory). - `python -m pkg.mod` - the current working directory. - `python -c '...'`, a script piped on stdin, or the interactive prompt - an empty string, which the import system resolves as the current working directory. That single entry is the whole mechanism behind name shadowing. A `logging.py` sitting next to `app.py` is found before the standard library's `logging` package, and so is a `json.py`, a `types.py`, a `queue.py`, a `secrets.py`, a `select.py`, a `copy.py`, an `email.py` or the perennial `test.py`. ### Why the failure is confusing The symptom is usually not `ImportError`. Your file imports fine; it simply is not the module anyone wanted. So the failure surfaces later and elsewhere: - `AttributeError: module 'logging' has no attribute 'getLogger'`, raised from inside a dependency that imported the name for its own use, nowhere near your file. - A partially-initialized-module error, when your `logging.py` itself does `import logging` (directly or through another module) and re-enters a module that is still executing. - No error at all: your file is close enough that the program runs and silently loses behaviour. The one-line diagnosis is to print the module's origin - `import logging; print(logging.__file__)` - which names the offending file immediately. Printing `sys.path[0]` shows the prepended entry that let it in. ### The fixes, in order of preference **Rename the file.** Shadowing a standard-library name is a naming defect, not an interpreter defect, and no flag makes the name safe to keep. Nothing else in the list removes the problem for people who run your code some other way. **Start the interpreter with `-P`, or set `PYTHONSAFEPATH=1`.** Both landed in Python 3.11 and mean the same thing: do not prepend that potentially unsafe path at all. Under them `sys.path` simply lacks the script directory (or the working directory, or the empty string), so the standard library is reached first. `sys.flags.safe_path` is `1` when either is in effect, which lets a program assert its own launch conditions. **Use `-I` when you want the whole isolation package.** Isolated mode implies `-P` along with `-E` and `-s`, so it covers this case plus the environment and the per-user site directory. ### Three caveats worth carrying First, `-E` makes the interpreter ignore `PYTHON*` environment variables, and `PYTHONSAFEPATH` is one of them - so `-E` on its own actively disables the environment-variable form of the fix. If both matter, pass `-P` (or `-I`) on the command line, where it cannot be ignored. Second, `-P` is per process. A child you launch with `subprocess` inherits your environment but not your command-line flags, so an isolated parent can still spawn a shadowed child unless you pass the flag along. Third, `-P` with `-m` changes how you run code out of a checkout. Suppressing the working-directory entry is exactly what stops the shadowing, and it is also what makes `python -P -m mytool` fail when `mytool` exists only as a directory in the current folder. That is a signal to install the project (an editable install is the usual answer) rather than to relying on the working directory being importable. ### Two myths to retire Deleting the file does not repair a process that already imported it: the module object lives in `sys.modules` and other modules hold direct references to it, so only a restart (or a deliberate `importlib.reload` of everything involved) helps. And a leftover `__pycache__/logging.cpython-314.pyc` cannot keep shadowing after the source is gone. Since the cache-directory layout was introduced, a cached bytecode file inside `__pycache__` is only ever used alongside its source file; it is not importable on its own.
- If PYTHONSAFEPATH is exported and you also pass -E, which one wins?`-E` wins, in the sense that it discards the variable. `-E` tells the interpreter to ignore `PYTHON*` environment variables, and `PYTHONSAFEPATH` is one of them, so the safe-path behaviour is simply not enabled. If you want both effects, pass the flags on the command line - `-P -E`, or just `-I`, which implies both - because command-line options are never subject to `-E`.
- A colleague deletes the shadowing file while the service is running and the wrong module is still in use. Why?Because the import already happened. The module object is cached in `sys.modules` and every module that did `import logging` holds a direct reference to that object, so removing the file changes nothing for the live process. Only a restart reliably fixes it; `importlib.reload` would have to be applied to the shadowing module and to everything holding a name bound from it, which is rarely worth attempting.
- Does -P change anything for a module you import from a directory listed in PYTHONPATH?No. `-P` only suppresses the one entry the interpreter prepends for you - the script's directory, the working directory, or the empty string. Entries that come from `PYTHONPATH` are untouched and still sit ahead of the standard library. Removing those requires `-E` (ignore `PYTHON*` variables) or `-I`, which implies it.
The interpreter answers an import the way you answer a knock at the door: it takes the first person standing there, not the one you were expecting. Putting your own file at the front of the queue means the standard library never gets asked.
saying these in an interview costs you the question
- Thinks the standard library is always searched first
- Says sys.path is searched alphabetically or by shortest name
- Blames a stale .pyc file rather than the shadowing source file
- Deletes the file and expects the running process to recover
- Believes PYTHONPATH must be set for shadowing to happen
- Suggests renaming the stdlib import instead of the local file