# Use a with block to control export (public/private/\_\_all\_\_)

**URL:** <https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722>\
**Category:** Ideas\
**Created:** [August 24, 2026, 7:45am UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722 "2026-08-24T07:45:14Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![hlovatt](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hlovatt/32/36838_2.png) [@hlovatt](https://discuss.python.org/u/hlovatt)\
**Post date:** [August 24, 2026, 7:45am UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722/1 "2026-08-24T07:45:14Z")

</div>

Presently, there is a lot of debate about how to do export control. An alternative suggestion is an `export` context manager that remains compatible with ` __all__ `:

```python
with export(): # Everything declared inside export blocks is exported.
    import math
    import sys as sys_alias
    CONST = 42

def not_exported(): pass # Outside block, not exported

with export(): # Second block.
    def func(): return CONST
    print(func()) # You can put a non-declaration statement inside, but bad style!

print( __all__ ) # 'sys_alias', 'CONST', 'math', 'func'

```

The `export` context manager works by snapshotting globals on entry and exit and adding the diff to ` __all__ `.

If this is something people are interested in, happy to write up and put on PyPi so people ‘can try before they buy’.

---

<div class="post-metadata">

**Author:** ![hlovatt](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hlovatt/32/36838_2.png) [@hlovatt](https://discuss.python.org/u/hlovatt)\
**Post date:** [August 24, 2026, 7:53am UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722/2 "2026-08-24T07:53:16Z")

</div>

There is currently a pole running about visibility signalling [Poll: Public/internal handling — what behavior do we want?](https://discuss.python.org/t/poll-public-internal-handling-what-behavior-do-we-want/108706) . There are also plenty of discussions [PEP 842 postmortem: how do we protect the standard library? - #44 by danielhollas](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/44) , [PEP 842](https://discuss.python.org/t/pep-842-module-exports-new-revision/108460), [PEP 843](https://discuss.python.org/t/pep-843-export-statement-for-dry-re-exports/108687), [PEP 844](https://discuss.python.org/t/pep-844-public-and-private-builtins/108515), and [using two namespaces](https://discuss.python.org/t/a-two-namespace-model-for-python-modules-to-distinguish-public-internal/108593).

---

<div class="post-metadata">

**Author:** ![Antti](https://avatars.discourse-cdn.com/v4/letter/a/e5b9ba/32.png) [@Antti](https://discuss.python.org/u/Antti)\
**Post date:** [August 24, 2026, 7:57am UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722/3 "2026-08-24T07:57:33Z")

</div>

I’m using a similar context manager for lazy imports in Python 3.11–14 (see pypi for ”katso”) and in my experience it makes both the intent clear and is straight forward to implement.

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [August 24, 2026, 7:59am UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722/4 "2026-08-24T07:59:25Z")

</div>

> [@hlovatt](#):
>
> The `export` context manager works by snapshotting globals on entry and exit and adding the diff to ` __all__ `.

Seems a bit fragile, and dependent on a CPython implementation detail (unless you have some way to implement `export` without `sys._getframe`). Maybe this is something that needs actual language support - a namespace block. The nearest we currently have is a class, but that comes with a lot of cruft; you can’t just grab every name out of the class and export it. But it comes close:

```python
class exports:
    import math
    import sys as sys_alias
    CONST = 42
__all__ = [x for x in dir(exports) if not x.startswith("_")]

```

A true namespace could provide the functionality needed.

---

<div class="post-metadata">

**Author:** ![hlovatt](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hlovatt/32/36838_2.png) [@hlovatt](https://discuss.python.org/u/hlovatt)\
**Post date:** [August 24, 2026, 8:01am UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722/5 "2026-08-24T08:01:38Z")

</div>

Katso looks great - haven’t seen it before. 😀

---

<div class="post-metadata">

**Author:** ![hlovatt](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hlovatt/32/36838_2.png) [@hlovatt](https://discuss.python.org/u/hlovatt)\
**Post date:** [August 24, 2026, 8:20am UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722/6 "2026-08-24T08:20:01Z")

</div>

Yes, using `_getframe`. Would need to see what other implementations could do. I only use CPython so not familiar with them. Might be in luck, they might have something similar.

Your other suggestion of inferring what should and shouldn’t be exported based on a leading underscore has been tried, e.g. `populate_all` in the `atpublic` project. You end up with a complicated algorithm otherwise functions you import from other modules get exported and functions that are meant to by exported but are spelt with an underscore (e.g. `NamedTuple`) don’t. Also you end up using `_getframe`!

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [August 24, 2026, 8:25am UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722/7 "2026-08-24T08:25:59Z")

</div>

> [@hlovatt](#):
>
> Your other suggestion of inferring what should and shouldn’t be exported based on a leading underscore has been tried, e.g. `populate_all` in the `atpublic` project.

Right, I’m saying that a `class` block is far from perfect but is the closest we get without actual language support. Suppose that, instead, we had something that actually captured all assignment, but wasn’t a function or a class:

```python
namespace export:
    import math
    import sys as sys_alias
    CONST = 42
# Calls export(["math", "sys_alias", "CONST"], globals())

```

This would execute the block of code in the surrounding namespace as normal, but would capture the list of names assigned to within the block, and then call the given callable (in this case, `export`) with that list, and the enclosing namespace. I don’t think there’s a straight-forward way to do this without some form of language support.

---

<div class="post-metadata">

**Author:** ![hlovatt](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hlovatt/32/36838_2.png) [@hlovatt](https://discuss.python.org/u/hlovatt)\
**Post date:** [August 24, 2026, 8:35am UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722/8 "2026-08-24T08:35:00Z")

</div>

Yeah a namespace is an option, it is mentioned in the pole running currently. A disadvantage is that it doesn’t ‘play nice’ with ` __all__ `, something the with export block does. So for example, outside of the export block ` __all__ ` can be manually manipulated. People needing to manually managing ` __all__ ` is something that derailed PEP 482 (see [postmortem](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/44)).

---

<div class="post-metadata">

**Author:** ![UnknownUser03393](https://avatars.discourse-cdn.com/v4/letter/u/f9ae1b/32.png) [@UnknownUser03393](https://discuss.python.org/u/UnknownUser03393)\
**Post date:** [August 24, 2026, 1:26pm UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722/9 "2026-08-24T13:26:34Z")

</div>

maybe you should change a name because many scripts used the name `export`.  
maybe `decl_public`? or a more pythonic name .  
request for comments.

---

<div class="post-metadata">

**Author:** ![UnknownUser03393](https://avatars.discourse-cdn.com/v4/letter/u/f9ae1b/32.png) [@UnknownUser03393](https://discuss.python.org/u/UnknownUser03393)\
**Post date:** [August 24, 2026, 1:28pm UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722/10 "2026-08-24T13:28:11Z")

</div>

also, this would be extremely hard to analyze for type checkers like mypy.  
because it made the **all** from a static list to a dynamic code block,  
and it is not clear at all

---

<div class="post-metadata">

**Author:** ![UnknownUser03393](https://avatars.discourse-cdn.com/v4/letter/u/f9ae1b/32.png) [@UnknownUser03393](https://discuss.python.org/u/UnknownUser03393)\
**Post date:** [August 24, 2026, 1:29pm UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722/11 "2026-08-24T13:29:41Z")

</div>

the current ` __all__ `'s contract is based on that “the ` __all__ ` is static”,  
this make it 100% dynamic (or it is on the opposite meaning of `with` block),  
this is also on the opposite meaning of ` __all__ `

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [August 24, 2026, 1:34pm UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722/12 "2026-08-24T13:34:03Z")

</div>

> [@UnknownUser03393](#):
>
> the current ` __all__ `'s contract is based on that “the ` __all__ ` is static”,

What do you mean?

In general, you should be able to assume that, by the time you’re importing the module, it is stable. That’s not the same thing as static, and notably, there are a LOT of ways to dynamically add things to ` __all__ ` during the execution of the module. They’re fine.

That aspect of this proposal isn’t an issue. I think it’s a good idea, but just going to be a tad fragile without language support - and that language support isn’t to fix the ` __all__ ` side of things 🙂

---

<div class="post-metadata">

**Author:** ![UnknownUser03393](https://avatars.discourse-cdn.com/v4/letter/u/f9ae1b/32.png) [@UnknownUser03393](https://discuss.python.org/u/UnknownUser03393)\
**Post date:** [August 24, 2026, 1:39pm UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722/13 "2026-08-24T13:39:30Z")

</div>

sorry for that error.  
but on the other way, how can we add it?  
in `builtins.export`? that would destory a large amount of scripts using `export` (it’s the keyword of UNIX Bash), with a weird error like `NameError` or `SyntaxError` if it’s a keyword.  
In my opinion, the keyword `with`_hints_ that the code in this block will be executed **runtime** , that’s not what the `static analyze` mean.

---

<div class="post-metadata">

**Author:** ![UnknownUser03393](https://avatars.discourse-cdn.com/v4/letter/u/f9ae1b/32.png) [@UnknownUser03393](https://discuss.python.org/u/UnknownUser03393)\
**Post date:** [August 24, 2026, 1:41pm UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722/14 "2026-08-24T13:41:02Z")

</div>

btw, the human readers and the static analyzers _hopes_ that ` __all__ ` is staticly declared.

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [August 24, 2026, 1:45pm UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722/15 "2026-08-24T13:45:05Z")

</div>

> [@UnknownUser03393](#):
>
> btw, the human readers and the static analyzers _hopes_ that ` __all__ ` is staticly declared

Hmm, I’ve never had a problem with it as a human reader. It’s usually pretty clear.

---

<div class="post-metadata">

**Author:** ![UnknownUser03393](https://avatars.discourse-cdn.com/v4/letter/u/f9ae1b/32.png) [@UnknownUser03393](https://discuss.python.org/u/UnknownUser03393)\
**Post date:** [August 24, 2026, 2:30pm UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722/16 "2026-08-24T14:30:48Z")

</div>

sure. ` __add__ ` is pretty.  
but

```python
with export():
    from somepkg import somefunc
    from os import system

```

it’s kinda weird.

---

<div class="post-metadata">

**Author:** ![Antti](https://avatars.discourse-cdn.com/v4/letter/a/e5b9ba/32.png) [@Antti](https://discuss.python.org/u/Antti)\
**Post date:** [August 24, 2026, 5:04pm UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722/17 "2026-08-24T17:04:46Z")

</div>

What top-level module code isn’t executed at runtime?

Why would someone assume code running inside a context manager somehow changes the timing of its execution?

---

<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 25, 2026, 12:39am UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722/18 "2026-08-25T00:39:44Z")

</div>

While a technically clever solution. I find the indenting through the context manager distracting. From habit, I expect functions to not be indented. It’s particularly confusing that exported functions have a different indentation level than not-exported functions.

---

<div class="post-metadata">

**Author:** ![hlovatt](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hlovatt/32/36838_2.png) [@hlovatt](https://discuss.python.org/u/hlovatt)\
**Post date:** [August 25, 2026, 8:24am UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722/19 "2026-08-25T08:24:26Z")

</div>

> [@UnknownUser03393](#):
>
> ```python
> with export():
> from somepkg import somefunc
> 
> ```

This means that `somefunc` can be used inside your module (the one you are writing that contains the `with export`) and is also exported by the module, so that users of your module do not also have to import `somefunc` as well.

Other proposals in this space have proposed `from somepkg export somefunc` for this functionality.

---

<div class="post-metadata">

**Author:** ![hlovatt](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hlovatt/32/36838_2.png) [@hlovatt](https://discuss.python.org/u/hlovatt)\
**Post date:** [August 25, 2026, 8:28am UTC](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722/20 "2026-08-25T08:28:15Z")

</div>

> [@timhoffm](#):
>
> It’s particularly confusing that exported functions have a different indentation level than not-exported functions.

I like this feature because it reminds me they are special - however it is definitely a matter of personal taste.

[Next page](https://discuss.python.org/t/use-a-with-block-to-control-export-public-private-all/108722.md?page=2)
