PEP 842 – Module Exports
- Author:
- Peter Bierma <peter at python.org>
- Discussions-To:
- Discourse thread
- Status:
- Draft
- Type:
- Standards Track
- Created:
- 25-Jul-2026
- Python-Version:
- 3.16
- Post-History:
- 24-Jul-2026, 31-Jul-2026, 07-Aug-2026
Table of Contents
- Abstract
- Motivation
- Specification
- Rationale
- Backwards Compatibility
- Security Implications
- How to Teach This
- Reference Implementation
- Rejected Ideas
- Open Issues
- Acknowledgements
- Change History
- Copyright
Abstract
This PEP proposes an export statement that modules can use to express
intent about the visibility of variables from outside the module.
For example:
# spam.py
from mypackage export name
export foo = "42"
export class Public:
pass
class Private:
pass
>>> import spam
>>> 'Public' in dir(spam)
True
>>> 'Private' in dir(spam)
False
>>> spam.Public
<class 'spam.Public'>
>>> spam.Private
Traceback (most recent call last):
File "<python-input-4>", line 1, in <module>
spam.Private
ExportError: 'Private' is not exported by 'spam'
This is not intended to be an access modifier for Python; see the rationale.
Motivation
Module-level names need privacy
A developer is writing a Python module. The module is intended to have one
“public” class – a class that is intended for users of the module – called
PublicAPI. As part of implementing PublicAPI, the developer wants to
create another class, called Helper. However, Helper is not meant to
be public in the same way that PublicAPI is public. Helper is supposed
to only be used by the developer of the module – a “private” API.
Nonetheless, the developer declares the two classes as such:
# spam.py
class Helper:
...
class PublicAPI:
...
The problem with this is that Helper comes with no indication that it’s not
a public API. It shows up in autocomplete by language servers, the dir()
function, Python’s interactive help() function, and every other API meant
for introspection. How are users supposed to know that they aren’t supposed to
use this?
Prefixed names aren’t necessarily a great solution
In Python, the convention for declaring private names is to prefix them
with _. So, the developer changes Helper into _Helper:
# spam.py
class _Helper:
...
This is generally the standard for Python libraries today, but it’s not clear that this is the best long term solution. This works (with some caveats; see the sections below), but this is (subjectively) less readable, and does require more keystrokes by the maintainer. Ideally, users shouldn’t be tempted to reach for private names from modules in the first place.
However, it is acknowledged that this idea is going against 30 years of
convention; even if this PEP is accepted, it’s expected that “underscored”
names (names prefixed with a leading _) will remain a staple of Python
for years to come. The purpose of this PEP is not to eliminate the need for
_ in module-level names, but instead to clear up corner cases where a
private name is ambiguous or tempting. In other words, this PEP is intended
to improve expressiveness and clarity with private APIs, not to add brand
new functionality.
It’s not always clear where names need prefixing
Python defines names through many different constructs, some of which are not always clear or intuitive to the developer. As a result, it can be difficult to remember where names need to be prefixed. To put this issue into perspective, imagine that a developer wants to import some other modules in their code:
# spam.py
import argparse
import asyncio
import tabnanny
In the above example, the spam module will have argparse, asyncio,
and tabnanny as seemingly public attributes. In practice, this is not good
for a maintainer, because maintainers may want to remove and change imports as
they please, so these attributes should not be treated as public APIs.
Python’s standard library currently sidesteps this problem through a note in
the backwards compatibility policy (PEP 387) that states that imported
modules are not considered public APIs and may change at any time, but
unfortunately, users are unable to determine this without directly reading
the backwards compatibility policy, which is not a common thing to do.
The solution to this is to also prefix every imported name with _:
# spam.py
import argparse as _argparse
import asyncio as _asyncio
import tabnanny as _tabnanny
But, again, this sprinkles the code with even more underscored names, and doesn’t necessarily send a crystal-clear message that the name is private; see the next section.
Prefixed names are not a universal rule
As modules evolve, some underscored names are made public, either because users did not clearly understand that an underscore indicated instability, or because users found useful functionality in a module’s private API, and nothing was discouraging them from using it.
In the standard library, a prime example of this is the ctypes module.
ctypes is full of stable APIs that are subject to Python’s backwards
compatibility policy, but contain a leading underscore. For example:
This sends the wrong message to consumers of the API. When seeing things like this in a codebase, it makes it seem like the code is opting out of backwards compatibility, or that an underscored name does not mean “private” in the module. In both cases, consumers are inclined to reach for more private names (because there’s no apparent consequence for doing so), making this problem worse.
Modules aren’t immune to this problem either. The standard _thread module,
for example, is prefixed with _ while being public.
Some libraries have native counterparts
In some cases, prefixing an import with _ makes it ambiguous, because
some complicated modules come with extension modules
that provide access to native functionality or otherwise speed up the module
in some way. These native modules are often prefixed with a leading underscore.
For example, in CPython, the asyncio module has a private
_asyncio accelerator module, so a reader seeing _asyncio may take it
to mean the C accelerator and not the normal module.
Imports are suggested by language servers and linters
Circling back to the issue described earlier, imports defined at the module-level are visible as “public” names to the API surface. In fact, when developing a module, the autocomplete provided by language servers will often suggest importing modules that were also imported by that module. So, not only are users not prevented from accessing seemingly-public imports, they may be encouraged to do so by their language server! (This problem applies to any name that is meant to be private; it’s just that imports are a particularly common case for this to occur.)
Real-world cases
This is not a hypothetical problem. There are many real examples of this causing issues in practice.
Note
Special thanks to Hugo van Kemenade for compiling this list.
os.errno
In Python 3.7, an import to the errno module was removed from os.
This caused a lot of breakage:
botocore.vendored
The botocore package had vendored
dependencies under the botocore.vendored namespace, which ended up
being relied upon by users:
SciPy and pandas
Both the SciPy and pandas packages had other packages visible at the module-level, which had to be deprecated and removed due to third-party usage:
scikit-learn
The scikit-learn package vendored
six and joblib. Downstream packages then used those vendored copies
and broke when they were removed in v0.23:
Linters cannot fight against imports
As a solution to the above problem, one might suggest that linters should
simply warn against importing modules from another module. The primary issue
with this is that this pattern is particularly common in __init__.py files
to move all packages into one namespace. For example:
# __init__.py
from my_package import subpackage_1
from my_package import subpackage_2
# etc
Linters have no language-level way to distinguish this pattern from “standard”
imports. As a solution, many linters use import name as name to identify
intentional re-exports, but this pattern is only a convention. For example,
the above __init__.py would be rewritten as this:
# __init__.py
from my_package import subpackage_1 as subpackage_1
from my_package import subpackage_2 as subpackage_2
# etc
Not only is this redundant (and a violation of the DRY principle), it’s confusing! Python’s official documentation does not document this pattern for re-exports (because it’s not defined by the language and is only a convention enforced by linters), so the packages that do this are primarily just “in the know”.
But, because this is only a convention, linters can’t enforce the negative case;
if an import is not given the name as name treatment, a linter can’t necessarily
assume that an import is not a re-export.
We want to be nice to users, not shrug them off
When a user decides to use a private API, accidentally or not, they will inevitably be broken by the library author. In many cases, this results in a bug report asking for the API to be fixed or restored to prevent downstream breakage. In this case, the library maintainer has to make a decision:
- Tell the user that they’re in the wrong for using it, and allow the breakage to take place.
- Commit to maintaining the private API as public, increasing the burden on themselves and encountering some of the problems described in the motivation.
This PEP is not intended to solve this problem entirely, but instead is meant to mitigate it by making it much clearer that a user is accessing a private name; in other words, this PEP wants to decrease (or eliminate) the amount of accidental private API usage in practice. By accessing a private API, the user must make a conscious decision to do so.
Library consumers use runtime introspection for documentation
A counterargument to the above section is that a library should clearly document what is private and what is public. In theory, yes, but in practice, users don’t read the documentation in full.
A common practice when designing APIs is to design for intuition. If an API is named and placed well, then a user often won’t need to reach for the documentation. Python is no exception to this.
When prototyping, it’s typical for someone to use dir() or help()
in Python’s interactive REPL to look for attributes that are useful to
them. In this case, if something is intuitive enough for the user, they will
simply reach for it without checking the documentation first. In a language
as dynamic as Python, the way people consume APIs is also dynamic.
__all__ is only a convention
The fundamental issue here is that Python has no way to express which names in a module are “private” or “public”. Prefixing is an option, but given the reasons above, it’s not always a bulletproof solution for library authors.
Currently, the other convention for expressing which names are public is
done through a module’s __all__ variable. This has two major downsides:
__all__often gets out of sync, because as developers add, change, or remove names from their module, there is often nothing pushing them towards changing__all__, because again, using it to list public names is only a convention and not enforced by anything.__all__is not always exhaustive. See the rejected ideas for examples of where the items in__all__might only be a subset of the “public” names in a module. In short, it can be difficult to control namespace pollution and declare all public names in__all__simultaneously.
This PEP intends to solve both of these problems with a new __export__ variable
and export statement.
Specification
The ExportError type
A new exception type, called ExportError, is added to the builtins
module. ExportError inherits from AttributeError.
Though allowed, it is not intended to be raised by user code; instead, it is
meant to be raised by a module object when accessing
a name that is not in __export__; see Module attribute access.
C API
Note
This section is specific to CPython.
The ExportError class will be added to the public C API headers under
the name PyExc_ExportError. As with all other global exception types,
it will be in the Stable ABI and will be immortal
at runtime.
__export__ variables
Requirements
When defined in a module’s global scope, __export__ must be assigned to an
instance of list (or a subclass of it) containing str objects:
__export__ = ["name1", "name2", "name3"]
Item requirements
The strings inside __export__ are not required to correspond to names defined
in the module, though there is no practical reason to include undefined names.
For example, the following is valid (as in, it will not generate an exception
at runtime), with one caveat:
__export__ = ["does not exist"]
The caveat is that this will raise an exception when used with a wildcard import
(from module import *), because __all__ is implicitly set by __export__;
see Implicit __all__ definitions.
Module attribute access
When __export__ is present in a module’s globals, all access to attributes
present on the module object will also check if the attribute name is present
in __export__ (via __contains__ or through iteration, as specified previously).
If the attribute name is not present in __export__, then an ExportError
is raised. For example:
# spam.py
a = 42
b = 24
__export__ = ["a"]
>>> import spam
>>> spam.a
42
>>> spam.b
Traceback (most recent call last):
File "<python-input-2>", line 1, in <module>
spam.b
ExportError: 'b' is not exported by 'spam'
Note
This also affects from imports, because those use the same attribute
access mechanism.
Dunder names
This does not apply to dunder names; attributes such as __dict__
and __file__ will always be accessible on the module through
attribute access, even if they are not included in the module’s __export__.
For example:
# spam.py
__export__ = []
>>> import spam
>>> spam.__name__
'spam'
Module __getattr__ functions
The behavior of __export__ cannot be overridden by a module’s
__getattr__() function, as __getattr__ functions are only
invoked for undefined names on modules. However, in cases where a module
__getattr__ is invoked, __export__ has no effect. For example:
# spam.py
__export__ = ["exported"]
exported = 42
def __getattr__(name):
if name == "exported":
# This is never triggered!
raise ImportError()
if name == "hello":
# "hello" is never put through the __export__ filter
return 42
raise AttributeError(f"{__name__!r} has no attribute {name!r}")
>>> import spam
>>> spam.__export__
["exported"]
>>> spam.exported
42
>>> spam.hello
42
__dir__ behavior
On a module with __export__, the module’s __dir__() function
will be modified to exclude names that are not in the module’s __export__.
As with attributes, this behavior does not apply to dunder names; those will always
be included in the output of dir(), regardless of whether the names are included
in __export__. For example:
# spam.py
class Public:
...
class Private:
...
__export__ = ["Public"]
>>> import spam
>>> dir(spam)
['Public', '__builtins__', '__doc__', '__export__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
User-defined module __dir__ functions
If a module defines its own __dir__ method, it takes precedence
over this behavior. It is up to the implementer of __dir__ to exclude
names that are not present in __export__. For example:
# spam.py
a = 42
b = 24
__export__ = ['a']
def __dir__():
return list(globals().keys())
>>> import spam
>>> dir(spam)
[..., 'a', 'b']
Implicit __all__ definitions
If a module defines __export__ but does not define __all__,
then __all__ will be assigned to __export__. To visualize:
# spam.py
a = 42
b = 24
c = 'c'
__export__ = ['a', 'b']
# __all__ is implicitly set to ['a', 'b'], so 'c' will not be included in
# wildcard imports.
>>> from spam import *
>>> a
42
>>> b
24
>>> c
Traceback (most recent call last):
File "<python-input-3>", line 1, in <module>
c
NameError: name 'c' is not defined
This means that the
previously specified requirements for
__export__ are not exhaustive, as __export__ in this case must also
be a valid __all__. For example, including a name in __export__ that does
not exist in the module will break wildcard imports:
# spam.py
a = 42
__export__ = ['a', 'noexist']
>>> from spam import *
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
from spam import *
AttributeError: module 'spam' has no attribute 'noexist'
Semantic implementation
For a module, defining __export__ is roughly equivalent to adding the
following code:
if "__all__" not in globals():
__all__ = __export__
def _is_dunder_name(name):
return (len(name) > 4) and name.startswith("__") and name.endswith("__")
# Attributes not in the __dict__ fall back to the normal lookup
def __getattribute__(name):
try:
value = globals()[name]
except KeyError:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from None
if _is_dunder_name(name):
return value
if name not in __export__:
raise ExportError(f"{name!r} is not exported by {__name__!r}")
return value
_module = sys.modules[__name__]
# This is a spooky magic function -- pretend it exists for example's sake
patch(_module, '__getattribute__', __getattribute__)
def __dir__():
names = []
for name in globals().keys():
if (name in __export__) or _is_dunder_name(name):
names.append(name)
return names
Exporting names
Grammar
The grammar is changed to allow for the standalone export statement and
export assignments:
export_stmt[stmt_ty]:
| "export" ','.NAME+
| "export" assignment
simple_stmt[stmt_ty] (memo):
| assignment
| &"export" export_stmt
Note that augmented assignments (x += y) are disallowed through a PEG action
at compile time.
Standalone exports
A standalone export statement is a shorthand for appending one or more names
to a global __export__ list.
When the export statement is used, the interpreter first checks if each
name exists in the global scope. If any do not exist, a NameError is
raised. The interpreter then checks if an __export__ variable exists in
the global namespace. If not, it is assigned to an empty list object.
Then, for each name used in the export statement, a str containing
the name of the variable is passed as the first positional argument to the
__export__.append method.
To visualize, the following code:
export NAME1, NAME2
is semantically equivalent to:
if "NAME1" not in globals():
raise NameError(...)
if "NAME2" not in globals():
raise NameError(...)
try:
__export__
except NameError:
__export__ = []
__export__.append("NAME1")
__export__.append("NAME2")
The export statement is only allowed in the global namespace; using it
elsewhere (such as inside of a function body) raises a SyntaxError
during compilation.
Export assignments
When an assignment statement is prefixed with export, the name is defined
and then exported.
As an example, the following code:
export NAME1, NAME2 = VALUE1, VALUE2
is semantically equivalent to:
NAME1 = VALUE1
NAME2 = VALUE2
export NAME1, NAME2
“Export assignment” statements are valid when used with standard assignment
statements (a = b, a, b = c, d, etc), and individual assignments
that contain a type annotation (a: type = b; in contrast, a standalone
export a: type is not valid). For example, each of the following is valid:
export hello = "world"
export my, hovercraft = "full of", "eels"
export types_work_too: int = 42
The following are NOT valid:
export hello: str
export my: str, hovercraft: str = "full of", "eels"
export name := "walrus"
export hello += "world"
Exporting functions and classes
Grammar
export_compound_stmt[stmt_ty]:
| "export" (function_def | class_def)
compound_stmt[stmt_ty]:
| &"export" export_compound_stmt
The export keyword must be the first token in a class or def
statement; it cannot be put after def or class. For example, the
following is not valid:
def export name(): # NOT VALID
...
class export Name: # NOT VALID
...
Behavior
A function definition or class definition statement can be prefixed with
export to automatically export the name.
To visualize, the following code:
export def NAME1():
...
export class NAME2:
...
is semantically equivalent to:
def NAME1():
...
export NAME1
class NAME2:
...
export NAME2
As with assignments and standalone exports, using export def or
export class outside of the global scope will raise a SyntaxError
at compile time.
There are no other caveats; all other syntax features of classes and functions
work when prefixed with export.
The module re-export statement
Grammar
A new rule is added and the existing import_from rule is modified:
import_or_export[expr_ty]:
| 'import'
| "export"
import_from[stmt_ty]:
| "lazy"? 'from' ('.' | '...')* dotted_name import_or_export import_from_targets
| "lazy"? 'from' ('.' | '...')+ import_or_export import_from_targets
Behavior
The “module re-export statement” is an extension to the behavior of the
from imports; it does the exact same thing, but also exports each of
the imported names.
For example, the following code:
from MODULE export NAME1, NAME2
is semantically equivalent to:
from MODULE import NAME1, NAME2
export NAME1, NAME2
Similar to the other export constructs, this must occur at the module-level;
using it elsewhere is a SyntaxError.
Lazy imports, as described by PEP 810, are also allowed to be used with from
exports. For example:
lazy from foo export bar
The existing rules for lazy imports apply here as well.
Rationale
This is not an access modifier
This PEP does not aim to be a mechanism for preventing access to private
attributes in modules. The ExportError can be bypassed (such as by
accessing attributes through the module’s __dict__).
This is by design. Python does not include access modifiers as a language feature for a reason. To quote Eric Smith: “Access to internals of other classes is a feature when you need it”. This PEP does not intend to change this convention, nor should it be interpreted as an indication that Python is moving toward true access modifiers.
Instead, the intention of this PEP is to improve clarity when inspecting modules at runtime, which should, in turn, improve the maintainer experience of Python modules in the long term.
Backwards Compatibility
This does not require changes to existing code
The functionality described in this PEP is only activated when a module defines
__export__ in the global scope (or by using the export statement, which
implicitly defines __export__). Modules that do not do this will experience
the current behavior, where every name is exported by default.
__export__ overloads
This PEP has the potential to break users who were already defining global
variables called __export__. That said, the Python language reference
explicitly forbids users from doing this in the first place.
export (soft) keyword
export, as proposed by this PEP, is a soft keyword.
It does not break backwards compatibility, meaning that existing code using
“export” as a variable name will continue to work.
Security Implications
This PEP has no known security implications.
How to Teach This
Both the export statement and the __export__ variable will
be documented as part of the language standard.
Maintaining backwards compatible codebases
To help adoption, it will be recommended that users define both __all__
and __export__ in their modules. This allows code on Python 3.16+ to get
the proper export behavior, while older versions still keep their __all__
attribute. In practice, this should look something like this:
__all__ = ["hovercraft"]
__export__ = __all__ + ["eels"]
Or, if the package’s __all__ is equivalent to __export__:
__export__ = __all__
Comparison to JavaScript’s export keyword
In JavaScript, names are private by default, so the export keyword
means “make this name public”. This idea does not apply well to Python, because
in Python, names are public (as in, importable) by default, except when
__export__ is present in a module’s namespace. So, a clearer definition
for export in Python is “make everything else private except for this name”.
Reference Implementation
A reference implementation of this PEP can be found here.
Performance
The reference implementation does not currently implement any optimizations
to reduce the overhead of the __export__ lookup or iteration, meaning
that there is likely some overhead. However, if this PEP is accepted,
optimizations will be implemented before the feature lands in CPython.
Rejected Ideas
Reuse __all__ for exports
Instead of adding a new __export__ variable, an alternative was to
reuse __all__ for names.
This was ultimately decided against because it seemed clear that there were
cases where a name could be in __export__, but not in __all__. The
primary example for this case was with static typing. For example, a module
may define several type aliases that would pollute a namespace if used with
a wildcard import, so the developer chooses to not include them in __all__,
but users of static typing will still want access to these type aliases for
annotating their own code.
In addition, it’s not clear that there’s any good spelling for this
behavior that covers all cases. The “obvious” solution is to add a
new future statement that makes __all__ more strict, but
that isn’t backwards compatible; codebases wanting to opt in to the behavior
described by this PEP must use a spelling that works on all supported Python
versions in order to keep their code working on older versions, so any
solutions that add special functionality to __all__ generally will not
work.
Emitting a warning upon accessing unexported attributes
This PEP initially proposed raising an ImportError upon accessing
module attributes that were not listed in __export__. This was not
well received, as the PEP did not clearly describe the intentions behind
the proposal, and as such, many rejected the notion of “private attributes”
as a knee-jerk reaction. Following that feedback, the ImportError turned
into a warning, which was eventually determined to be a bad compromise.
Introduce __export__ on its own
The original revision of this proposal included __export__ as a standalone
variable and did not provide any new syntax. The appeal of this was that it
was backwards compatible; projects could simply write __export__ = __all__,
and then when users upgraded to a version that supported __export__, they
would get the documentation and enforcement benefits described by this PEP.
It was eventually decided that this was too conservative, because while
__export__ was compatible with __all__, it shared many of the same
problems with it, such as forgetting to add or remove items from the list.
To quote Guido van Rossum:
But the ergonomics are similar to those of__all__, and those are bad. It’s too easy to forget to add (or remove!) something to the list, and it’s distracting to have to update the export info in a totally different part of a file than the definition of the exported thing.
Add a private keyword for class bodies
During discussion of this proposal, it was suggested to add a private
keyword for use in classes (or to allow export in class bodies).
For example:
class Something:
private def hello(self):
print("Hello, world!")
export def goodbye(self):
print("Goodbye, world!")
This was rejected primarily because it does not have a clear benefit over
the existing name mangling behavior (using
the __ prefix), which also solves many of the problems described in the
motivation of this PEP.
Additionally, this is much more difficult to implement. The author’s reference
implementation involved new access protocols, disabling optimizations, and overall
much more complexity when compared to the simple modification to the default
module.__getattribute__ behavior required by __export__.
Add public and private decorators as builtins
Instead of adding a new export keyword, it was suggested to add private
and public decorators, based on Barry Warsaw’s atpublic
package, to the builtins module.
The decorators would have provided the same documentation aspect of this PEP, and potentially the same enforcement aspect, without the need for new syntax. For example:
@public
class MyPublicClass:
...
# Or
@private
class MyPrivateClass:
...
This is the author’s next preferred solution after export syntax, but it
does come with some caveats. In particular, there’s no easy way to export
simple variables without duplicating the name, which many dislike due to the
violation of the DRY principle.
Nonetheless, this is specified in a competing proposal: PEP 844.
Make module names public by default
Many have argued that the current behavior of this proposal is counterintuitive,
because in Python, names are public by default. This means the semantics of export
break down when compared to other languages (see how JavaScript handles
this). As a solution, it was proposed to keep
names public by default and add a private keyword (along with a
__private__ list) to describe which names are private. For example:
def public_name():
...
private def private_name():
...
This was rejected because it makes the intended goal of this proposal (making it clearer which names in a module are public) much harder to achieve in practice.
The purpose of this proposal is to avoid leaking private names into the public
namespace, but with the proposed __private__ feature, the developer has to know
and think about every possible name that can be defined. export does not have
this problem; the developer thinks about the few names that need to be public,
and then never has to think about privacy again. It is expected that if
__private__ were used instead of __export__, then module authors would
often forget to mark a name as private, and thus users would be led to believe
that a private name is public.
It is also mechanically more difficult for developers, as large modules tend to have many more private names than public names, through imports, helper functions, and similar.
Finally, it is not believed that export is difficult to understand. As
previously mentioned, export in Python doesn’t match the definition for
export in JavaScript, but beyond that, understanding what export does,
and why, seems fairly comprehensible for Python users.
Open Issues
How should packages have access to their own private members?
Imagine that a package has two modules:
library/_utils.py, which is meant to contain utilities that are only for the developer oflibrary.library/main.py, which holds public APIs that are usable to the users oflibrary.
The names in _utils.py are not exported, because the module is not intended
to be accessed by users of library. But, main.py should have access to
these names; the current proposal would result in main.py getting an
ExportError upon importing private names from _utils.py.
How should this be resolved? Is this necessary at all – as in, should _utils.py
mark its utilities as exported, and ask that users don’t import anything from it?
Acknowledgements
Thanks to Hugo van Kemenade and Savannah Ostrowski for inspiring the idea behind this PEP.
In addition, the design behind this PEP was largely influenced by discussion and ideas from many people, including, but not limited to, Guido van Rossum, Paul Moore, Steve Dower, and Barry Warsaw.
Change History
- 11-Aug-2026
- Required
__export__to always be alistobject. - Clarified some parts of the PEP based on questions from the discussion thread.
- Added the
ExportErrorbuiltin type (removingExportWarning), and switched back to raising an exception upon accessing unexported names.
- Required
- 05-Aug-2026
- Added an
exportstatement. - Added the
ExportWarningbuiltin type, which is now emitted instead of aRuntimeWarningwhen accessing unexported attributes.
- Added an
- 01-Aug-2026
- Accessing an unexported attribute now emits a
RuntimeWarninginstead of raising anImportError. - Significantly overhauled the motivation section.
- Accessing an unexported attribute now emits a
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.