We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Interfaces with Protocols and ABCs step 20 of 26
Work Out What __all__ and Re-exports Make Public
Two tools decide which of a module's names other code gets, and they use different rules.
- At runtime,
from widgets import *copies names into the importing module. Ifwidgetsdefines__all__, Python copies exactly the names listed there. Without__all__, it copies every name that does not start with an underscore, including names the module only imported. - mypy in strict mode decides which names other modules may import from
widgets. It treats an imported name as private unless the module re-exports it. To re-export a name, import it with a same-name alias, such asfrom decimal import Decimal as Decimal, or list it in__all__.
Take this module:
# widgets.py
import json
from dataclasses import dataclass
from decimal import Decimal as Decimal
@dataclass
class Widget:
price: Decimal
from widgets import * copies Decimal, Widget, dataclass and json.
mypy lets other modules import Decimal and Widget, and rejects
from widgets import json with
Module "widgets" does not explicitly export attribute "json".
What to write
Write solve, which works out both sets of names from a description of a
module, plus the names in __all__ that the module never defines or
imports:
def solve(
defined: list[str],
imported: list[str],
reexported: list[str],
declared_all: list[str] | None,
) -> dict[str, object]: ...
definedholds the names the module defines.importedholds the names it imports without re-exporting them.reexportedholds the names it imports with a same-name alias.declared_allis the contents of__all__, orNonewhen the module has no__all__. An empty list means__all__ = [], which exports nothing.
The module's bound names are all the names in the first three lists.
Return three lists, each in sorted order:
| Key | Rule |
|---|---|
star_exports |
Without __all__, the bound names that do not start with _, each once. With __all__, the entries of declared_all that are bound. |
undefined_in_all |
Without __all__, []. With __all__, the entries of declared_all that are not bound. |
checker_private |
The entries of imported that are in none of defined, reexported and declared_all. A missing __all__ counts as empty here. |
Details the tests check
- Keep repeated entries when you filter
declared_allorimported. IfdefinedholdsAanddeclared_allis["A", "A"],star_exportsis["A", "A"]. - A star import copies a name that starts with
_when__all__lists it. The underscore rule applies only when there is no__all__. - A name can appear in more than one input list. A name in
definedorreexportednever goes inchecker_private. - Sort with plain
sorted. Uppercase letters sort before_, and_before lowercase letters, so the order is"Path","_secret","dataclass". - Treat the names as plain strings, and leave the input lists unchanged.
assert solve(["Widget"], ["json", "dataclass"], ["Decimal"], None) == {
"star_exports": ["Decimal", "Widget", "dataclass", "json"],
"undefined_in_all": [],
"checker_private": ["dataclass", "json"],
}
assert solve(["Widget"], ["json", "dataclass"], ["Decimal"], ["Widget", "Wiget"]) == {
"star_exports": ["Widget"],
"undefined_in_all": ["Wiget"],
"checker_private": ["dataclass", "json"],
}
The second case is a real bug that undefined_in_all catches. mypy accepts
__all__ = ["Widget", "Wiget"], and from widgets import * then raises
AttributeError. Test the imports your package promises, as well as running
mypy.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.