# Export Statement for DRY Re-exports

**URL:** <https://discuss.python.org/t/export-statement-for-dry-re-exports/108444>\
**Category:** Ideas\
**Created:** [August 6, 2026, 2:26pm UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444 "2026-08-06T14:26:26Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![NeilGirdhar](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/neilgirdhar/32/5776_2.png) [@NeilGirdhar](https://discuss.python.org/u/NeilGirdhar)\
**Post date:** [August 6, 2026, 2:26pm UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444/1 "2026-08-06T14:26:26Z")

</div>

Building a public layout API from a library’s internal layout usually means one of two things today:

1. Writing every exported name twice, once in an import and again as a string in ` __all__ `.
2. The reflexive-alias idiom, `from x import y as y`, which type checkers understand but which reads like a typo to everyone else, and which leaves ` __all__ ` empty, so you lose `dir()` cleanliness and wildcard-import control.

I’d like to propose a small addition that removes both: a `from <module> export <name> [as <alias>]` statement. It does exactly what `from <module> import <name> [as <alias>]` does, and also appends the name to ` __all__ ` in the same statement.

```python
# spam/ __init__.py
from ._internal.core export PublicAPI
from ._internal.widgets export Widget as PublicWidget

```

Nothing is left to sync by hand, and no alias needs decoding. It composes with control flow the same way import does, and supports a wildcard form for the common two-tier layout (an internal module curates its own ` __all__ `, a hub module does `from ._internal.core export *`), plus a lazy variant that builds on PEP 810.

Relationship to PEP 842: this grew directly out of that thread. Both proposals land on the same syntax for the module re-export case, `from MODULE export NAME`, arrived at independently, which I take as a good sign it’s the right shape. Where they differ: PEP 842 also adds export forms for standalone names, assignments, def, and class, backed by a new ` __export__ ` variable and an `ExportWarning` on non-exported attribute access. This proposal deliberately covers only the re-export case.

Relationship to PEP 844: just posted, and worth reading alongside this one. It proposes `public()`/`private()` builtins for names a module defines, the same ground `atpublic` covers today. Its own text notes that it doesn’t solve re-exports well (`public(Widget=Widget`) still names `Widget` three times) and calls this proposal a good companion for exactly that gap: `public()`/`private()` for names a module defines, export for names a module passes through. Its _Open Issues_ section asks how PEP 842, 843, and 844 should be reconciled, so that’s probably the central question for this thread.

Quick comparison:

| | PEP 842 | PEP 843 | PEP 844 |
| --- | --- | --- | --- |
| Mechanism | New syntax (five forms of the `export` keyword) | New syntax (one form of the `export` keyword) | New builtins; no grammar change |
| Scope | Definitions and re-exports | Re-exports | Definitions |
| Export list | A new ` __export__ ` list, and ` __all__ ` | ` __all__ ` | ` __all__ ` |
| Runtime enforcement | Yes: `ExportWarning` on access to non-exported attributes | None | None |

Full draft of the proposal with explanation is here: [PEP 843 – Export Statement for DRY Re-exports | peps.python.org](https://pep-previews--5078.org.readthedocs.build/pep-0843/index.html)

Feedback welcome, especially on the one open question in the draft: should `export *` require the source module to define its own ` __all__ `, or fall back to `import *`'s no-all behavior (bind everything without a leading underscore)?

---

<div class="post-metadata">

**Author:** ![petercordia](https://avatars.discourse-cdn.com/v4/letter/p/bb73d2/32.png) [@petercordia](https://discuss.python.org/u/petercordia)\
**Post date:** [August 6, 2026, 2:48pm UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444/2 "2026-08-06T14:48:59Z")

</div>

For what it’s worth, I like this one.\[1\]

Could it be worth also adding some behaviour to `export` that helps IDEs and type-checkers, like perhaps modifying the metadata of exported objects, or removing plain imports from `dir`?

* * *

1. More than the other two, by a significant margin.

---

<div class="post-metadata">

**Author:** ![pf\_moore](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pf_moore/32/35_2.png) [@pf\_moore](https://discuss.python.org/u/pf_moore)\
**Post date:** [August 6, 2026, 3:51pm UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444/3 "2026-08-06T15:51:14Z")

</div>

> [@NeilGirdhar](#):
>
> Building a public layout API from a library’s internal layout usually means one of two things today

I don’t think those are the only two common options.

For example, `rich` exposes an API that’s distributed across a number of submodules. `packaging` does the same. Similarly, `scipy` and `sympy` have a module structure to their public API. To be honest, I think a nested structure is probably _more_ common than a flat one for larger projects.

While I’m not saying it isn’t worth improving things for projects that _do_ follow the pattern you describe, I think it’s overstating the case to suggest that people “usually” adopt a pattern that will benefit from what you’re proposing.

---

<div class="post-metadata">

**Author:** ![NeilGirdhar](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/neilgirdhar/32/5776_2.png) [@NeilGirdhar](https://discuss.python.org/u/NeilGirdhar)\
**Post date:** [August 6, 2026, 4:21pm UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444/4 "2026-08-06T16:21:40Z")

</div>

> [@pf\_moore](#):
>
> Similarly, `scipy` and `sympy` have a module structure to their public API. To be honest, I think a nested structure is probably _more_ common than a flat one for larger projects.

Sorry if this was confusing, but the “hub-and-internal model” I described doesn’t imply flatness. It often does have submodules. You can see an example of this in the PEP where we talk about lazily importing a submodule. Each submodule has its own hub (an ` __init__.py`) that can expose various objects and even more nested submodules.

SciPy is one of the many examples of this, and if you look at its various ` __init__.py` files, you can see how this PEP would collapse most of the ` __all__ ` maintenance that it does.

For example: See [in a sobmodule](https://github.com/scipy/scipy/blob/main/scipy/optimize/ __init__.py), replacing the imports that are exported with `export` would eliminate ` __all__ `'s maintenance. But, nicely, one import would remain—thereby distinguishing semantically the exports from a true import.

Or if we look at its [top-level module](https://github.com/scipy/scipy/blob/main/scipy/ __init__.py), this code:

```python
from scipy. __config__ import show as show_config
from scipy.version import version as __version__

submodules = [
    'cluster',
    'constants',
    'datasets',
    # ...
]

__all__ = submodules + ['show_config', ' __version__']

def __dir__ ():
    return __all__

def __getattr__ (name):
    if name in submodules:
        return _importlib.import_module(f'scipy.{name}')

```

could _ **all** _ be replaced by a more elegant:

```python
from scipy. __config__ export show as show_config
from scipy.version export version as __version__
lazy from . export cluster
lazy from . export constants
lazy from . export datasets

```

No clever machinery needed.

> [@pf\_moore](#):
>
> For example, `rich` exposes an API that’s distributed across a number of submodules.

I have to admit, I didn’t understand what Rich was doing when I looked at it. It has a variety of modules that define objects. All of the modules are exposed in `dir`, but not all of them are part of the public interface in the docs. Within the modules that compose the public interface, there are various objects. These are explicitly exported. They’re just there and documented. Therefore, Rich won’t benefit from any of the PEPs: 842, 843, or 844.

However, if Rich did want to set it’s ` __all__ ` lists, it could do that maybe more nicely with PEP 844 or 842. This PEP won’t help it.

> [@pf\_moore](#):
>
> I think it’s overstating the case to suggest that people “usually” adopt a pattern that will benefit from what you’re proposing.

I looked at about 20 projects (including Rich and SciPy), but if you want to survey more projects, please feel free to share your results.

> [@petercordia](#):
>
> Could it be worth also adding some behaviour to `export` that helps IDEs and type-checkers, like perhaps modifying the metadata of exported objects, or removing plain imports from `dir`?

This is a really good idea, but I wanted to keep the proposal as modest as possible while hitting all the objectives. Feel free to discuss in this thread any ideas you might have about modifying metadata though. I’ve seen people edit the ` __module__ `, but I haven’t thought through all of the consequences of doing that unconditionally.

---

<div class="post-metadata">

**Author:** ![jamestwebber](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jamestwebber/32/12799_2.png) [@jamestwebber](https://discuss.python.org/u/jamestwebber)\
**Post date:** [August 6, 2026, 4:24pm UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444/5 "2026-08-06T16:24:59Z")

</div>

It’s a minor point, but I think I’d find the meanings of `import` and `export` sort of muddled. In normal language they are antonyms, but here `export` means “`import` and add to ` __all__ `”.

The proposed syntax is satisfyingly succinct, but I don’t think that it conveys the meaning of the statement well.

---

<div class="post-metadata">

**Author:** ![xitop](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/xitop/32/13242_2.png) [@xitop](https://discuss.python.org/u/xitop)\
**Post date:** [August 6, 2026, 5:53pm UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444/6 "2026-08-06T17:53:23Z")

</div>

> [@jamestwebber](#):
>
> but I think I’d find the meanings of `import` and `export` sort of muddled

Apologies to all native speakers for me discussing English terms, but “reexport” seems to me a good description of what that statement is doing.

---

<div class="post-metadata">

**Author:** ![pf\_moore](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pf_moore/32/35_2.png) [@pf\_moore](https://discuss.python.org/u/pf_moore)\
**Post date:** [August 6, 2026, 5:54pm UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444/7 "2026-08-06T17:54:37Z")

</div>

> [@NeilGirdhar](#):
>
> I looked at about 20 projects (including Rich and SciPy), but if you want to survey more projects, please feel free to share your results.

You didn’t comment on `packaging`, which doesn’t use that pattern. I’m not going to do a more extensive survey, though, as it’s not really about the numbers. I think the point stands, not every project would benefit from this and I’m not sure it’s “common”. That doesn’t mean it’s useless, I just think you should be careful not to overstate its applicability.

---

<div class="post-metadata">

**Author:** ![barry](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/barry/32/42_2.png) [@barry](https://discuss.python.org/u/barry)\
**Post date:** [August 6, 2026, 6:01pm UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444/8 "2026-08-06T18:01:26Z")

</div>

> [@NeilGirdhar](#):
>
> Quick comparison

Very nice chart, thanks!

---

<div class="post-metadata">

**Author:** ![timhoffm](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/timhoffm/32/19468_2.png) [@timhoffm](https://discuss.python.org/u/timhoffm)\
**Post date:** [August 6, 2026, 6:45pm UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444/9 "2026-08-06T18:45:37Z")

</div>

Open questions:

- Which is the better logic? Marking specific elements as “public” (e.g. through export) or marking other elements as “private”.
- Do we need or want to create a dedicated namespace for the public interface? The design here is based on the existing two-tier pattern of public “hub namespaces” that do not contain logic, but only import and re-expose symbols from internal namespaces. An alternative would be that there is only hierarch hierarchy and every module contains a set of public and a set of private variables; e.g. one imlementation option would be that ` __dict__ ` contains the public variables and ` __private_dict__ ` contains the private variables. The latter would in addition only be checked when looking up from within the module scope (actual storage and design up to bike shedding; and yes opt-in external access to ` __private_dict__ ` is still possible either directly or through some syntactic sugar).

---

<div class="post-metadata">

**Author:** ![OutSquareCapital](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/outsquarecapital/32/35076_2.png) [@OutSquareCapital](https://discuss.python.org/u/OutSquareCapital)\
**Post date:** [August 6, 2026, 7:32pm UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444/10 "2026-08-06T19:32:19Z")

</div>

Nested or not, every project will in fact benefit from this, as soon as they declare an **all** somewhere.

* * *

Thankfully now ruff take into account this automatically (at least in **init**.py files) but i’d rather have a solution like your proposal instead of depending on a tool to handle my exports

---

<div class="post-metadata">

**Author:** ![bwoodsend](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/bwoodsend/32/4027_2.png) [@bwoodsend](https://discuss.python.org/u/bwoodsend)\
**Post date:** [August 6, 2026, 9:08pm UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444/11 "2026-08-06T21:08:51Z")

</div>

> [@NeilGirdhar](#):
>
> Quick comparison:

TBH, I don’t think it’s really meaningful to compare the proposals like this given that they have different goals. PEP 842 seeks to address 3 concerns:

1. Functions/classes/variables that aren’t supposed to be used but are still visible in the namespace
2. Imports _leaking_ into the namespace of packages that import them
3. This idea that the underscore prefix convention isn’t enough and that we need something more aggressive

(Concern 2 is the only one that gains any traction for me personally, and mostly only for single file modules where the `import sys as _sys` workaround is quite annoying.)

843 focuses only on 2 but only for packages\[1\] and only handling ` __all__ `\[2\]. `requests.urllib3` is still there and still looks like public API to anyone using completion to explore a library.

Incidentally, if you compare the contents of the `numpy` namespace with `numpy. __all__ `, there’s nothing in there that would have been exported without ` __all__ `.

```python
>>> set(dir(numpy)) - set(numpy. __all__ )
{'_NoValue', '_array_api_info', ' __all__', ' __getattr__', ' __config__', ' __cached__', ' __future_scalars__', ' __spec__', '_specific_msg', ' __builtins__', '_core', '_expired_attrs_2_0', '_utils', ' __package__', ' __dir__', ' __doc__', ' __expired_attributes__', ' __loader__', '_pyinstaller_hooks_dir', ' __NUMPY_SETUP__', '_type_info', '_int_extended_msg', ' __file__', '_distributor_init', ' __numpy_submodules__', '_msg', ' __path__', '_CopyMode', ' __name__', '_typing', '_globals', '_pytesttester', '_mat', ' __array_api_version__', ' __former_attrs__'}

```

So numpy is actually a perfect example of going to great lengths to define ` __all__ ` when you could just delete it and your problem is solved.

Whilst I’m not completely dismissing [your arguments](https://discuss.python.org/t/pep-842-module-exports/108353/165) for still defining ` __all__ ` even when you’ve already taken advantage of the ` __init__.py` to create a clean, curated namespace, I don’t find them nearly compelling enough to justify a language change.

* * *

1. which need it much less than single file modules 

2. which I consider to be overused and largely unnecessary

---

<div class="post-metadata">

**Author:** ![NeilGirdhar](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/neilgirdhar/32/5776_2.png) [@NeilGirdhar](https://discuss.python.org/u/NeilGirdhar)\
**Post date:** [August 6, 2026, 9:21pm UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444/12 "2026-08-06T21:21:20Z")

</div>

> [@bwoodsend](#):
>
> Incidentally, if you compare the contents of the `numpy` namespace with `numpy. __all__ `, there’s nothing in there that would have been exported without ` __all__ `.

> [@bwoodsend](#):
>
> So numpy is actually a perfect example of going to great lengths to define ` __all__ ` when you could just delete it and your problem is solved.

Since my explanation didn’t convince you, you might want to actually try this: delete ` __all__ ` and see what extra things get pulled in from a wildcard import, and what’s missing. 😃

---

<div class="post-metadata">

**Author:** ![pawamoy](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pawamoy/32/5811_2.png) [@pawamoy](https://discuss.python.org/u/pawamoy)\
**Post date:** [August 6, 2026, 9:34pm UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444/13 "2026-08-06T21:34:43Z")

</div>

Nice PEPs! Thanks for bringing this topic forward 🙂

> **Me when I started reading PEP 844:**
>
> ![image](https://us1.discourse-cdn.com/flex002/uploads/python1/original/3X/7/0/707ee87e557814565db05f1c43f308b79b4bbdb8.jpeg)
> 
> I wish I had found [this paragraph](https://docs.python.org/3/reference/simple_stmts.html#import:~:text=The%20public,module%29%2E) earlier. It deserves much, much, much more visibility.

I end up agreeing with PEP 844: combined with 843, they form a nice way of declaring public APIs. 843 for re-export specifically (flat/nested layouts, but esp. flat ones), 844 for layouts where some modules are public and there’s no re-export (probably).

I’m not sold on how 844 declares public assignments though. None of `public(SEVEN=7)` or `SEVEN = public(SEVEN=7)` look good to me (`SEVEN = public(7)` would look better, but requires horrible tricks to get the symbol name).

Also, it doesn’t offer a way to mark class symbols as public. Those still require the underscore prefixing convention. It’s a bit like PEP 702: you can mark a few things as deprecated, but not all, and not in a very flexible way… So IMO it should stay a third-party library. I’d prefer the standard library to incorporate complete solutions, not half ones 😕 In that regard, 842 is maybe a way to _extend_ ` __all__ ` to support class symbols too.

As usual, writing as author of [Griffe](https://mkdocstrings.github.io/griffe/guide/users/recommendations/public-apis/), a tool that extracts API data from Python source and understands ` __all__ ` and other conventions.

---

<div class="post-metadata">

**Author:** ![barry](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/barry/32/42_2.png) [@barry](https://discuss.python.org/u/barry)\
**Post date:** [August 6, 2026, 11:53pm UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444/14 "2026-08-06T23:53:01Z")

</div>

> [@pawamoy](#):
>
> Also, it doesn’t offer a way to mark class symbols as public.

What does “class symbols” mean? Because `@public` can definitely be used with class definitions:

```python
% cat foo.py
from public import public

@public
class Public:
    pass

print(f'{ __all__ =}')

```

```python
$ python3 foo.py
% python3 foo.py
__all__ =['Public']

```

---

<div class="post-metadata">

**Author:** ![pawamoy](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pawamoy/32/5811_2.png) [@pawamoy](https://discuss.python.org/u/pawamoy)\
**Post date:** [August 7, 2026, 10:03am UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444/15 "2026-08-07T10:03:26Z")

</div>

That wasn’t clear, sorry, I meant symbols under a class definition:

```python
from public import public, private

@public
class Public:
    @public
    def hello():
        print("hello")

    @private
    def goodbye():
        print("bye")

```

The use case is documentation. For example “auto-docs” tooling needs to know if something is public or private to decide whether to render it in an HTML page.

---

<div class="post-metadata">

**Author:** ![barry](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/barry/32/42_2.png) [@barry](https://discuss.python.org/u/barry)\
**Post date:** [August 10, 2026, 5:52pm UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444/16 "2026-08-10T17:52:40Z")

</div>

> [@pawamoy](#):
>
> That wasn’t clear, sorry, I meant symbols under a class definition

As Guido says in another thread, class methods should be out of scope (and will be so explicitly in PEP 844).

---

<div class="post-metadata">

**Author:** ![pawamoy](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pawamoy/32/5811_2.png) [@pawamoy](https://discuss.python.org/u/pawamoy)\
**Post date:** [August 11, 2026, 9:20am UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444/17 "2026-08-11T09:20:22Z")

</div>

I see, thanks. I hope public interfaces for classes will be handled in a later PEP then. Do you remember in which thread I can find Guido’s reasoning?

---

<div class="post-metadata">

**Author:** ![barry](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/barry/32/42_2.png) [@barry](https://discuss.python.org/u/barry)\
**Post date:** [August 11, 2026, 3:19pm UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444/18 "2026-08-11T15:19:14Z")

</div>

You can see him reiterate that statement [here](https://discuss.python.org/t/pep-842-module-exports-new-revision/108460/54).

---

<div class="post-metadata">

**Author:** ![pawamoy](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pawamoy/32/5811_2.png) [@pawamoy](https://discuss.python.org/u/pawamoy)\
**Post date:** [August 11, 2026, 5:42pm UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444/19 "2026-08-11T17:42:17Z")

</div>

Thanks.

I agree with it being out of scope for PEPs about module exports (842 and 843). But in regards to PEP 844, which is more framed as “declaring public APIs”, I think it’s definitely in scope. I concede that 844 starts with “document the public interface of a module” (emphasis on module). I just wish it would go all the way and include public interface of classes too 🙂

Let me know if I should wait for a thread on 844 instead, I don’t want to hijack this one.

---

<div class="post-metadata">

**Author:** ![timhoffm](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/timhoffm/32/19468_2.png) [@timhoffm](https://discuss.python.org/u/timhoffm)\
**Post date:** [August 12, 2026, 12:30am UTC](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444/20 "2026-08-12T00:30:56Z")

</div>

> [@pawamoy](#):
>
> I concede that 844 starts with “document the public interface of a module” (emphasis on module). I just wish it would go all the way and include public interface of classes too 🙂

The public API topic is more difficult for modules than for classes.

IMHO the leading-underscore convention is basically enough for classes. - Or do you have cases that cannot reasonably be handled using underscores?

But module namespace serves two purposes: (1) being the externally visible API and (2) the module-global implementation namespace. This leads to import pollution. With `from foo import MyClass`, `MyClass` becomes part of the visible API even though it may only be needed for the implmentation. Unless we want to always rename on import `from foo import MyClass as _MyClass`, the underscore convention is not sufficient for modules.

This warrants focussing on module interfaces.

[Next page](https://discuss.python.org/t/export-statement-for-dry-re-exports/108444.md?page=2)
