正确的Python模块将在a list called __all__
中列出其所有公共符号。管理该列表可能很繁琐,因为您必须两次列出每个符号。当然有更好的方法,可能是using decorators,因此只需将导出的符号注释为 @export
。
你会怎么写这样的装饰者?我确定有不同的方法,所以我希望看到几个答案,其中包含足够的信息,用户可以将这些方法相互比较。
答案 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
缓存来访问模块,在某些更深奥的设置中,可能都存在问题(如自定义导入机制,或从另一个模块包装函数以在此模块中创建函数)。
__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())
该软件包中的函数很简单(几行),因此,如果要避免外部依赖性,可以将其复制到您的代码中。