What does a module's `__all__` list actually control in Python?
answer
- A list of strings, not a keyword
- It affects exactly one import form
- Hides nothing from direct access
- Type checkers read it in __init__.py
- A typo raises AttributeError on star import
basics
~20 s__all__ is a list of strings naming what from module import * binds. It hides nothing: direct attribute access and explicit imports still reach every name. Its real job is declaring the module's intended public API.
solid answer
~40 s`__all__` is a module-level list of name strings with exactly one runtime effect: it decides what `from module import *` binds. With it, star import takes those names and only those; without it, star import takes every module-level name that does not start with an underscore. It is not access control — `import archive; archive._compress` still works, and `from archive import _compress` still works. What makes it valuable is everything built on top of that declaration: it is the documented list of names you promise to keep, documentation tools render it, and type checkers treat a name listed in a package's `__init__.py` `__all__` as a deliberate re-export rather than an incidental import. A typo bites early — a listed name that does not exist raises `AttributeError` the moment someone star-imports the module.
code
pycon · 7 lines>>> import json
>>> json.__all__
['dump', 'dumps', 'load', 'loads', 'JSONDecoder', 'JSONDecodeError', 'JSONEncoder']
>>> "decoder" in json.__all__
False
>>> json.decoder.JSONDecoder is json.JSONDecoder
Truego deeper
Know that __all__ is a list of name strings that controls from module import *, and that it does not make anything private. Being able to read one in an unfamiliar package and say what the module promises is enough here.
Explain both branches of the star-import rule, that a listed underscore name is still exported, and that a bad entry raises AttributeError at star-import time. Know the __init__.py re-export pattern and why type checkers care about it.
Treat __all__ as the versioned contract: justify what you put in it, keep it static and reviewable, and describe the deprecation path you follow before a name leaves it in a shipped library.
Own the surface-area policy — how small a public list a shared library should keep, how the team reviews additions given each one is a permanent obligation, and what tooling stops the promised list and the documented list from drifting apart.
## The one runtime rule `__all__` is an ordinary module-level variable holding a list of strings. Its only effect on the interpreter is on the wildcard form of import: * If the module defines `__all__`, then `from archive import *` binds exactly the names it lists — including names starting with an underscore, and excluding public-looking names left out of the list. * If the module does not define `__all__`, star import binds every module-level name that does not start with an underscore. That is the whole mechanism. `__all__` does not restrict `import archive` followed by `archive.anything`, does not restrict `from archive import _compress`, and does not delete or hide anything. Anyone who calls it "Python's export keyword" and stops there has described a third of it and misdescribed the rest. One sharp edge follows directly from the rule: the names are *strings*, resolved at star-import time, so a misspelled entry is not caught when the module is imported normally. It surfaces as `AttributeError: module 'archive' has no attribute 'expotr'` the first time someone writes `from archive import *`. A linter that checks `__all__` entries against the module's real bindings catches this in CI, which is why most projects enable that rule. ## Why it matters far more than the rule suggests Star imports are discouraged in application code, so if `__all__` were only about them it would be a footnote. It is not, because `__all__` is the *machine-readable declaration of the module's public API*, and several things consume that declaration. **It is the contract.** The names in `__all__` are the ones you have promised: rename one, change its signature incompatibly, or drop it, and you have made a breaking change that needs a deprecation cycle and a version bump. Names outside it are yours to move. Writing the list forces the decision to be explicit rather than accidental, which is the difference between a library with nine public names and a library that accidentally promised ninety. **Type checkers use it for re-exports.** This is the practical reason a package's `__init__.py` needs one. A package usually keeps its code in implementation modules and lifts a few names into the package root: ```python # archive/__init__.py from ._core import Archive, export_transcript from ._compression import CompressionError __all__ = ["Archive", "export_transcript", "CompressionError"] ``` Under a strict type-checking configuration, a name merely imported into `__init__.py` is treated as a private implementation detail of that module, and a downstream `from archive import Archive` is reported as importing a non-exported name. Listing it in `__all__` (or writing the redundant-looking `from ._core import Archive as Archive`) marks it as an intentional re-export. So `__all__` in `__init__.py` is doing real work even in a codebase that never writes `import *`. **Documentation and IDEs use it.** API doc generators render the listed names as the module's reference surface; editors weight completions towards them. That, plus the underscore convention, is how Python communicates "public" without a keyword. ## How to write and maintain it Put `__all__` near the top of the module, as a plain list literal of string literals — keep it static so that tools can read it without executing the module. Sort it or group it by theme; either is fine, but pick one, because the diff on this list is exactly the diff of your public API and reviewers should be able to read it at a glance. Do not compute it (`__all__ = [n for n in dir() if ...]`) — a dynamic list defeats every static consumer and hides API changes from review. Do not append to it from elsewhere in the package for the same reason. Do not put submodules in it unless you really mean the star import to trigger their import; note that listing a submodule name in a package's `__all__` does cause `from pkg import *` to import that submodule. Finally, treat editing the list as an API event. Adding a name is a minor-release feature and an obligation; removing a name is a breaking change even when the underlying function still exists, because star importers lose the binding. That is a good thing: it puts a review gate exactly where the compatibility promise is made.
- Why does a package's `__init__.py` need `__all__` even in a codebase that bans star imports?Because a type checker in strict mode treats a name merely imported into `__init__.py` as private to that module, so downstream `from pkg import Thing` is flagged as importing a non-exported name. Listing it in `__all__` — or the explicit `from ._core import Thing as Thing` form — marks the import as an intentional re-export. It also gives documentation tools and reviewers a single readable list of what the package promises.
- Is removing a name from `__all__` while leaving the function in place a breaking change?Yes, on two counts. Anyone using `from pkg import *` loses the binding immediately, and you have withdrawn the published statement that the name is supported, which is the substantive part. Treat it as an API removal: deprecate first, remove in a major release. If the goal is only to tidy documentation, keep the name listed and mark it deprecated instead.
- Should `__all__` ever be computed at runtime rather than written as a literal?Almost never. A comprehension over `dir()` or conditional appends make the public surface invisible to type checkers, documentation tools and code review, and let an API change slip in without a reviewable diff. Keep it a static list of string literals near the top of the module; if platform-specific names must be conditional, prefer defining the module differently per platform over computing the list.
saying these in an interview costs you the question
- Says __all__ makes other names private or inaccessible
- Thinks __all__ affects `import module` or `from module import name`
- Believes __all__ contains objects rather than name strings
- Cannot say what star import does when __all__ is absent
- Computes __all__ dynamically from dir()
- Treats dropping a name from __all__ as a non-breaking cleanup