We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Structural Typing and the Hard Parts step 22 of 24
__all__, re-export rules, and the accidental public API
Without a declared surface, every module is public by accident. A user
discovers yourpkg.internal.helpers.frobnicate through autocomplete, imports
it, and it is load-bearing forever. Worse, an __init__.py that does
from .db import session has just re-published session — and from yourpkg import * will hand it out.
Three separate surfaces, and they do not agree
Runtime import *. With __all__ present, exactly those names — and
AttributeError at import time for any entry with no binding. Without
__all__, every bound name not starting with _, including everything the
module merely imported.
The type checker. --no-implicit-reexport (part of --strict) says
“imported symbols are considered private by default”. A name becomes
re-exported to the checker in exactly three ways:
-
import X as X -
from Y import X as X -
from Y import *whereYdefines__all__
Listing the name in __all__ also re-exports it. The redundant-looking
as X is the deliberate, spec-sanctioned marker for “yes, I meant to
publish this”.
__all__ itself. Here is the verified divergence: mypy --strict
does not validate __all__ contents. __all__ = ["Widget", "Typo"] with
no Typo anywhere passes cleanly. Pyright’s reportUnsupportedDunderAll
catches it. If mypy is your only checker, a typo’d __all__ is an
AttributeError at import for the first user who writes
from pkg import * — and your test suite, which imports by name, never sees
it.
Your task
Model the module namespace as data and compute all three surfaces.
def solve(
defined: list[str], # bound by def / class / assignment
imported: list[str], # bound by plain `from Y import Z`
reexported: list[str], # bound by `from Y import Z as Z`
declared_all: list[str] | None, # None means no __all__ at all
) -> dict[str, object]:
Return:
| key | value |
|---|---|
"star_exports" |
sorted names from pkg import * actually binds |
"undefined_in_all" |
sorted names in __all__ with no binding |
"checker_private" |
sorted bound names --no-implicit-reexport treats as private |
With __all__ present, star_exports is the listed names that are bound
(the rest land in undefined_in_all). With __all__ absent, it is every
bound name not starting with _ — note that this includes imports, which is
the leak.
A name is private to the checker iff it was imported plainly and is
neither re-exported with as nor listed in __all__. Names you defined are
always public to the checker.
Watch the sort: Python sorts by code point, so "Path" precedes
"_secret" precedes "dataclass".
The habit this encodes
Declare __all__ in every __init__.py, keep it accurate, and use
from .thing import Thing as Thing for deliberate re-exports. That one
convention gives you a surface a reviewer can read, a checker that agrees
with the runtime, and a deprecation story that does not start with “someone
imported our internals”.
Loading visualization…
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.