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__:
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’.
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.
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:
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.
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!
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:
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.
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).
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
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__
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
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 withhints that the code in this block will be executed runtime, that’s not what the static analyze mean.
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.
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.