@@ -14,7 +14,7 @@ Abstract
1414========
1515
1616This PEP proposes an ``__export__ `` variable that modules can define to
17- express intent behind visibility to variables from outside the module.
17+ express intent about the visibility of variables from outside the module.
1818
1919For example:
2020
@@ -52,8 +52,8 @@ Motivation
5252==========
5353
5454
55- Module names need privacy
56- -------------------------
55+ Module-level names need privacy
56+ -------------------------------
5757
5858A developer is writing a Python module. The module is intended to have one
5959"public" class -- a class that is intended for users of the module -- called
@@ -105,7 +105,7 @@ convention; even if this PEP is accepted, it's expected that "underscored"
105105names (names prefixed with a leading ``_ ``) will remain a staple of Python
106106for years to come. The purpose of this PEP is not to eliminate the need for
107107``_ `` in module-level names, but instead to clear up corner cases where a
108- private name is ambigious or tempting. In other words, this PEP is intended
108+ private name is ambiguous or tempting. In other words, this PEP is intended
109109to improve expressiveness and clarity with private APIs, *not * to add brand
110110new functionality.
111111
@@ -145,6 +145,8 @@ The solution to this is to also prefix every imported name with ``_``:
145145 import tabnanny as _tabnanny
146146
147147
148+ But, again, this sprinkles the code with even more underscored names.
149+
148150
149151.. _pep-842-prefixed-public :
150152
@@ -167,15 +169,15 @@ compatibility policy, but contain a leading underscore. For example:
167169This sends the wrong message to consumers of the API. When seeing things like
168170this in a codebase, it makes it seem like the code is opting out of backwards
169171compatibility, or that an underscored name does not mean "private" in the
170- module. In both cases, consumers are inclined to reach for more private names,
171- making this problem worse.
172+ module. In both cases, consumers are inclined to reach for more private names
173+ (because there's no apparent consequence for doing so), making this problem worse.
172174
173175
174176We want to be nice to users, not shrug them away
175177------------------------------------------------
176178
177179When a user decides to use a private API, accidentally or not, they will
178- inevitably be broken by the library author(s) . In many cases, this results
180+ inevitably be broken by the library author. In many cases, this results
179181in a bug report asking for the API to be fixed or restored to prevent
180182downstream breakage. In this case, the library maintainer has to make a decision:
181183
@@ -204,31 +206,32 @@ is named and placed well, then a user often won't need to reach for the
204206documentation. Python is no exception to this.
205207
206208When prototyping, it's typical for someone to use :func: `dir ` or :func: `help `
207- in Python's interactive :term: `REPL ` to look for attributes or methods that
208- are useful to them. In this case, if something is intuitive enough for the
209- user, they will simply reach for it without checking the documentation first.
210- In a language as dynamic as Python, the way people consume APIs is also dynamic.
209+ in Python's interactive :term: `REPL ` to look for attributes that are useful to
210+ them. In this case, if something is intuitive enough for the user, they will
211+ simply reach for it without checking the documentation first. In a language
212+ as dynamic as Python, the way people consume APIs is also dynamic.
211213
212214
213215``__all__ `` is only a convention
214216--------------------------------
215217
216218The fundamental issue here is that Python has no way to express which names
217- in a module are "private" or "public" -- or, in other words, which names are
218- designed to be stable APIs for users. Prefixing is an option, but given the
219+ in a module are "private" or "public". Prefixing is an option, but given the
219220reasons above, it's not always a bulletproof (or nice) solution for library
220221authors.
221222
222- Currently, the convention for expressing which names are public is done through
223- a module's :attr: ` __all__ ` variable. This has two major downsides:
223+ Currently, the other convention for expressing which names are public is
224+ done through a module's `` __all__ ` ` variable. This has two major downsides:
224225
2252261. ``__all__ `` often gets out of sync, because as developers add, change, or
226227 remove names from their module, there is often nothing pushing them towards
227228 changing ``__all__ ``, because again, using it to list public names is only
228- a * convention * and not enforced by anything.
229+ a convention and not enforced by anything.
2292302. ``__all__ `` is not always exhaustive. See the :ref: `rejected ideas
230231 <pep-842-all-for-exports>` for examples on where the items in ``__all__ ``
231- might only be a subset of the "public" names in a module.
232+ might only be a subset of the "public" names in a module. In short, it can
233+ be difficult to control namespace pollution and declare all public names in
234+ ``__all__ `` simultaneously.
232235
233236This PEP intends to solve both of these problems with a new ``__export__ `` variable.
234237
@@ -294,7 +297,7 @@ Module attribute access
294297When ``__export__ `` is present in a module's globals, all access to attributes
295298present on the module object will also check if the attribute name is present
296299in ``__export__ `` (via ``__contains__ `` or through iteration, as specified previously).
297- If the attribute name is not present in ``__export__ ``, then an :exc: `RuntimeWarning `
300+ If the attribute name is not present in ``__export__ ``, then a :exc: `RuntimeWarning `
298301is emitted. For example:
299302
300303.. code-block :: python
@@ -311,7 +314,7 @@ is emitted. For example:
311314 >>> spam.a
312315 42
313316 >>> spam.b
314- <python-input-4 >:1: RuntimeWarning: 'b' is not exported by 'spam'
317+ <python-input-2 >:1: RuntimeWarning: 'b' is not exported by 'spam'
315318 24
316319
317320
@@ -492,11 +495,13 @@ following code:
492495
493496.. code-block :: python
494497
495- __all__ = __export__
498+ if " __all__" not in globals ():
499+ __all__ = __export__
496500
497501 def _is_dunder_name (name ):
498502 return (len (name) > 4 ) and name.startswith(" __" ) and name.endswith(" __" )
499503
504+ # Attributes not in the __dict__ fall back to the normal lookup
500505 def __getattribute__ (name ):
501506 try :
502507 value = globals ()[name]
@@ -584,7 +589,7 @@ A reference implementation of this PEP can be found
584589`here <https://github.com/python/cpython/compare/main...ZeroIntensity:cpython:experiments/module-exports >`__.
585590
586591Performance
587- ***********
592+ -----------
588593
589594The reference implementation does not currently implement any optimizations
590595to reduce the overhead of the ``__export__ `` lookup or iteration, meaning
@@ -682,10 +687,11 @@ behind this PEP.
682687Change History
683688==============
684689
685- - 01-Aug-2026:
686- * Accessing an unexported attribute now emits a :exc: `RuntimeWarning ` instead
690+ * 01-Aug-2026
691+
692+ - Accessing an unexported attribute now emits a :exc: `RuntimeWarning ` instead
687693 of raising an :exc: `ImportError `.
688- * Significantly overhauled the motivation section.
694+ - Significantly overhauled the motivation section.
689695
690696
691697Copyright
0 commit comments