Builtin "export" decorator/function

It would be nice to not have to reiterate names when using __all__, and instead do something like

@export
def my_function():
    pass

@export
class MyClass:
    pass

which would append "my_function" and "MyClass" to __all__, creating it if not already present. This avoids reiterating exported names, and keeps the exportation near objects’ definitions. This is already possible in pure python, but having to import something to export something (cleanly) is strange and clunky. Additionally, it may be desirable to have something like

my_variable = 1
export(my_variable)

which would require reiterating my_variable but would keep its exportation near its definition. It might be possible to do this (and keep the decorator functionality) without making it an intrinsic function by calling various inspect functions and parsing the relative source to get the variable name and whether export is being used as a decorator or not, but that seems very overkill when the interpreter should be able to get all that information much more simply.

That’s an interesting idea. Of course, it (I think) would only work for classes and functions (stuff you can decorate). Something like this:

% python
Python 3.9.1 (default, Dec 11 2020, 14:32:07) 
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from export_example import export
>>> __all__
autoloading __all__
Error in sys.excepthook:
Traceback (most recent call last):
  File "/home/skip/misc/python/python3/autoload.py", line 35, in _exec
    exec(import_stmt, f_locals, f_globals)
  File "<string>", line 1, in <module>
ModuleNotFoundError: No module named '__all__'

Original exception was:
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name '__all__' is not defined
>>> @export
... def f(a):
...   return a
... 
>>> __all__
['f']
>>> 
>>> @export
... class X:
...   def __init__(self):
...     pass
... 
>>> __all__
['f', 'X']

Here’s the barebones export_example module. I’m sure the export decorator doesn’t adhere to recommended practice and doesn’t trap any user mistakes, but it demonstrates that it can do what you requested.

import inspect

def export(func_or_class):
    name = func_or_class.__name__
    caller = inspect.stack()[1]
    globals = caller.frame.f_globals
    if "__all__" not in globals:
        globals["__all__"] = []
    if name not in globals["__all__"]:
        globals["__all__"].append(name)
    return func_or_class

if all you care about is decorators, I believe it should be as simple as

import sys

def export(cls_or_func):
    module = sys.modules[cls_or_func.__module__]

    if not hasattr(module, "__all__"):
        module.__all__ = []

    module.__all__.append(cls_or_func.__name__)

    return cls_or_func

I do think it would be nice to be able to do export(my_variable) as well, but even just getting the decorator version as a builtin would be very nice.

I think you’re looking for something like atpublic · PyPI .

Ah, that is actually really clever using keyword arguments to mimic assignments. I’d still be interested in getting something like this builtin though, as having to import something to neatly export something is cumbersome, not to mention needing a dependency for what I feel should be a pretty basic thing for the language.

Even better. Just toss out my little hack.

Which brings up a corollary question. How are people supposed to find out what’s “good” in PyPI? There are so many packages…