From fc45cfe549daf0f5ac186c4ded2862c6ad46033e Mon Sep 17 00:00:00 2001 From: Neil Girdhar Date: Sat, 22 Aug 2026 15:48:57 -0400 Subject: [PATCH] PEP 843: Address open question and reviewer feedback Guido asked that export * behave exactly like import *, including its no-__all__ fallback, rather than special-casing the wildcard form; that resolves the PEP's one open question, so the Open Issues section is gone. Eneg found the __all__-creation rule too indirect, so it's now spelled out as three explicit cases with an example showing plain __all__ += still works after an export statement. jorenham asked that the PEP say plainly that export carries no restrictions of its own beyond import's, and call out if typing.TYPE_CHECKING: re-exports as an intended use case for stub-only packages like _typeshed. --- peps/pep-0843.rst | 83 ++++++++++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 30 deletions(-) diff --git a/peps/pep-0843.rst b/peps/pep-0843.rst index f5bd9d81101..49b35bbf4d9 100644 --- a/peps/pep-0843.rst +++ b/peps/pep-0843.rst @@ -194,6 +194,21 @@ Each branch runs its own import and its own ``__all__`` append, so the name that ends up exported depends on which branch ran, with no separate ``__all__`` bookkeeping required. +More generally, ``export`` is valid everywhere ``import`` is valid: +inside functions, classes, ``try`` blocks, and anywhere else a statement +can appear, with no restriction of its own. If ``export`` should be +limited in some context, ``import`` would need the same limit; that's +the subject of a separate proposal, not this one. + +In particular, using ``export`` in an ``if typing.TYPE_CHECKING:`` guard +lets stub-only packages such as ``_typeshed`` export a name that exists +in the stub but has no runtime counterpart. + +.. code-block:: python + + if typing.TYPE_CHECKING: + from ._internal.types export InternalOnly + ```` may be relative (``from .core export Thing``, ``from ..sub.core export Thing``) or absolute (``from numpy.typing export NDArray``), exactly as in an ordinary ``from ... import ...`` statement. @@ -242,6 +257,10 @@ This supports a common two-tier layout: an internal module curates its own ``__all__`` as it's written, and the hub re-exports that whole list in one statement, instead of naming each item again. +``export *`` matches ``import *``'s fallback when ```` defines +no ``__all__`` of its own. It exports every top-level name that doesn't +start with an underscore. + The wildcard form is equivalent to: .. code-block:: python @@ -360,11 +379,28 @@ Interaction with ``__all__`` A module may freely mix ``from ... export ...`` statements with a manually maintained ``__all__``, or with ``__all__ +=`` / -``__all__.append`` calls elsewhere in the file. Each ``export`` statement -simply appends to whatever ``__all__`` already exists in the module's -namespace, creating an empty list first if necessary. Duplicate names are -allowed: ``__all__`` was never required to be free of duplicates, and this -PEP doesn't change that. +``__all__.append`` calls elsewhere in the file. Each ``export`` +statement looks at whatever is currently bound to ``__all__`` in the +module's namespace before appending the new name(s): + +* If ``__all__`` doesn't exist yet, ``export`` creates it, as an empty + list. +* If ``__all__`` exists but isn't already a list, ``export`` copies it + into a list, preserving its existing contents. +* Otherwise ``__all__`` is already a list, and is used as is. + +The new name is then appended. So an ``export`` statement always leaves +``__all__`` as an ordinary, mutable list, one that later code in the +same module can keep extending with plain list operations: + +.. code-block:: python + + from .foo export Foo + + __all__ += ["Baz"] + +Duplicate names are allowed: ``__all__`` was never required to be free of +duplicates, and this PEP doesn't change that. ``from ... export ...`` affects only the contents of ``__all__``, which in turn affects ``from module import *`` and any tool that already reads @@ -608,31 +644,6 @@ This PEP considered two other spellings for the re-export statement: ``export`` as a prefix before several statement kinds. -Open Issues -=========== - -Should ``export *`` require the source module to define ``__all__``? --------------------------------------------------------------------- - -The two-tier layout that motivates the wildcard form (see -`Specification`_) depends on the internal module having deliberately -curated its own ``__all__``: that curated list is the reason the hub's -``export *`` is safe to write without naming each item. - -But ``from export *`` behaves exactly like ``from -import *``, which falls back, when ```` defines no ``__all__``, -to binding every top-level name that doesn't start with an underscore. -If a hub author writes ``export *`` against an internal module with no -``__all__``, that fallback silently re-exports whatever happens to lack -a leading underscore, names that may not have been curated as carefully -as an explicit ``__all__`` would require, and exactly the kind of -accidental export this PEP eliminates elsewhere. - -The open question: should ``export *`` follow ``import *``'s fallback as -is, or require ```` to define its own ``__all__`` and raise an -error if it doesn't? - - Acknowledgements ================ @@ -646,6 +657,18 @@ rather than hypotheticals. Change History ============== +* 22-Aug-2026 + + - Resolved the wildcard-form open question in favor of matching + ``import *`` exactly, including its no-``__all__`` fallback; removed + the now-resolved Open Issues section. + - Made the ``__all__``-creation rules in "Interaction with ``__all__``" + explicit, and added an example showing ``__all__ +=`` after an + ``export`` statement. + - Noted that ``export`` is usable anywhere ``import`` is, with no + restriction of its own, and called out ``if typing.TYPE_CHECKING:`` + re-exports as an intended use case for stub-only packages. + * 13-Aug-2026 - Reworded the public/implementation layout description in the