Export decorator that manages __all__

北城以北 提交于 2019-12-18 06:28:04

问题


A proper Python module will list all its public symbols in a list called __all__. Managing that list can be tedious, since you'll have to list each symbol twice. Surely there are better ways, probably using decorators so one would merely annotate the exported symbols as @export.

How would you write such a decorator? I'm certain there are different ways, so I'd like to see several answers with enough information that users can compare the approaches against one another.


回答1:


In Is it a good practice to add names to __all__ using a decorator?, Ed L suggests the following, to be included in some utility library:

import sys

def export(f):
    """Use a decorator to avoid retyping function/class names.

    * Based on an idea by Duncan Booth:
      http://groups.google.com/group/comp.lang.python/msg/11cbb03e09611b8a
    * Improved via a suggestion by Dave Angel:
      http://groups.google.com/group/comp.lang.python/msg/3d400fb22d8a42e1
    """
    mod = sys.modules[fn.__module__]
    if hasattr(mod, '__all__'):
        name, all_ = f.__name__, mod.__all__
        if name not in __all__:
            all_.append(name)
    else:
        mod.__all__ = [fn.__name__]
    return f

We've adapted the name to match the other examples. With this in a local utility library, you'd simply write

from .utility import export

and then start using @export. Just one line of idiomatic Python, you can't get much simpler than this. On the downside, the module does require access to the module by using the __module__ property and the sys.modules cache, both of which may be problematic in some of the more esoteric setups (like custom import machinery, or wrapping functions from another module to create functions in this module).

The python part of the atpublic package by Barry Warsaw does something similar to this. It offers some keyword-based syntax, too, but the decorator variant relies on the same patterns used above.

This great answer by Aaron Hall suggests something very similar, with two more lines of code as it doesn't use __dict__.setdefault. It might be preferable if manipulating the module __dict__ is problematic for some reason.




回答2:


You could simply declare the decorator at the module level like this:

__all__ = []

def export(obj):
    __all__.append(obj.__name__)
    return obj

This is perfect if you only use this in a single module. At 4 lines of code (plus probably some empty lines for typical formatting practices) it's not overly expensive to repeat this in different modules, but it does feel like code duplication in those cases.




回答3:


You could define the following in some utility library:

def exporter():
    all = []
    def decorator(obj):
        all.append(obj.__name__)
        return obj
    return decorator, all

export, __all__ = exporter()
export(exporter)

# possibly some other utilities, decorated with @export as well

Then inside your public library you'd do something like this:

from . import utility

export, __all__ = utility.exporter()

# start using @export

Using the library takes two lines of code here. It combines the definition of __all__ and the decorator. So people searching for one of them will find the other, thus helping readers to quickly understand your code. The above will also work in exotic environments, where the module may not be available from the sys.modules cache or where the __module__ property has been tampered with or some such.




回答4:


https://github.com/russianidiot/public.py has yet another implementation of such a decorator. Its core file is currently 160 lines long! The crucial points appear to be the fact that it uses the inspect module to obtain the appropriate module based on the current call stack.




回答5:


This is not a decorator approach, but provides the level of efficiency I think you're after.

https://pypi.org/project/auto-all/

You can use the two functions provided with the package to "start" and "end" capturing the module objects that you want included in the __all__ variable.

from auto_all import start_all, end_all

# Imports outside the start and end functions won't be externally availab;e.
from pathlib import Path

def a_private_function():
    print("This is a private function.")

# Start defining externally accessible objects
start_all(globals())

def a_public_function():
    print("This is a public function.")

# Stop defining externally accessible objects
end_all(globals())

The functions in the package are trivial (a few lines), so could be copied into your code if you want to avoid external dependencies.



来源:https://stackoverflow.com/questions/41895077/export-decorator-that-manages-all

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!