管理__all__的导出装饰器

时间:2017-01-27 13:27:52

标签: python module package visibility python-module

正确的Python模块将在a list called __all__中列出其所有公共符号。管理该列表可能很繁琐,因为您必须两次列出每个符号。当然有更好的方法,可能是using decorators,因此只需将导出的符号注释为 @export

你会怎么写这样的装饰者?我确定有不同的方法,所以我希望看到几个答案,其中包含足够的信息,用户可以将这些方法相互比较。

6 个答案:

答案 0 :(得分:3)

你可以简单地在模块级别声明装饰器,如下所示:

__all__ = []

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

如果您只在一个模块中使用它,这是完美的。在4行代码中(对于典型的格式化实践,可能还有一些空行),在不同的模块中重复这一行并不是太昂贵,但在这些情况下它确实感觉像代码重复。

答案 1 :(得分:3)

Is it a good practice to add names to __all__ using a decorator?中,Ed L建议将以下内容包含在某个实用程序库中:

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

我们已根据其他示例调整名称。有了这个在本地实用程序库中,您只需编写

from .utility import export

然后开始使用@export。只有一行惯用的Python,你不能比这简单得多。在缺点方面,模块确实需要使用__module__属性和sys.modules缓存来访问模块,在某些更深奥的设置中,可能都存在问题(如自定义导入机制,或从另一个模块包装函数以在此模块中创建函数)。

The python part atpublic packageBarry Warsaw做了类似的事情。它也提供了一些基于关键字的语法,但装饰器变体依赖于上面使用的相同模式。

This great answer Aaron Hall提出了一些非常相似的内容,还有两行代码,因为它没有使用__dict__.setdefault。如果出于某种原因操纵模块__dict__是有问题的,那可能更好。

答案 2 :(得分:1)

您可以在某个实用程序库中定义以下内容:

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

然后在你的公共图书馆里你会做这样的事情:

from . import utility

export, __all__ = utility.exporter()

# start using @export

使用该库需要两行代码。它结合了__all__和装饰器的定义。因此,搜索其中一个的人会找到另一个,从而帮助读者快速理解您的代码。以上内容也适用于异域环境,其中可能无法从sys.modules缓存或__module__属性被篡改的模块中获取模块。

答案 3 :(得分:1)

https://github.com/russianidiot/public.py还有另一个这样的装饰器实现。 Its core file目前有160行!关键点似乎是它使用inspect module来获取基于当前调用堆栈的适当模块。

答案 4 :(得分:1)

虽然其他变体在某种程度上在技术上是正确的,但也可以确保:

  • 如果目标模块已经声明了__all__,则可以正确处理;
  • 目标仅在__all__中出现一次:
# utils.py

import sys

from typing import Any


def export(target: Any) -> Any:
  """
  Mark a module-level object as exported.

  Simplifies tracking of objects available via wildcard imports.

  """
  mod = sys.modules[target.__module__]

  __all__ = getattr(mod, '__all__', None)

  if __all__ is None:
    __all__ = []
    setattr(mod, '__all__', __all__)

  elif not isinstance(__all__, list):
    __all__ = list(__all__)
    setattr(mod, '__all__', __all__)

  target_name = target.__name__
  if target_name not in __all__:
    __all__.append(target_name)

  return target

答案 5 :(得分:0)

这不是装饰器方法,但是可以提供我认为您追求的效率水平。

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

您可以使用软件包随附的两个功能来“开始”和“结束”捕获要包含在__all__变量中的模块对象。

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())

该软件包中的函数很简单(几行),因此,如果要避免外部依赖性,可以将其复制到您的代码中。