PEP 713 – 可调用模块
- 作者:
- Amethyst Reese <amethyst at n7.gg>
- 赞助者:
- Łukasz Langa <lukasz at python.org>
- 讨论列表:
- Discourse 帖子
- 状态:
- 已拒绝
- 类型:
- 标准轨迹
- 创建:
- 2023年4月20日
- Python版本:
- 3.12
- 历史记录:
- 2023年4月23日
- 决议:
- Discourse 消息
拒绝通知
指导委员会认为没有充分的理由支持此PEP,即使从一致性的角度来看它显然是可以实现的。如果这个想法将来再次出现,这是一个有用的先前讨论供参考。
摘要
模块目前不能直接调用。类可以定义一个__call__
方法,使实例对象可调用,但在全局模块作用域中定义一个同名函数则无效,并且该函数只能通过导入或直接引用module.__call__
来调用。PEP 562为模块添加了对__getattr__()
和__dir__()
的支持,但是定义__getattr__
来返回__call__
的值仍然不会使模块可调用。
此PEP建议通过在模块的全局命名空间中定义一个__call__
对象来支持使模块直接可调用,该对象可以是标准函数或任意可调用对象。
动机
许多模块只有一个主要的功能接口。在许多情况下,该接口是一个单一的可调用对象,能够直接导入和使用模块作为可调用对象为用户提供了更“Pythonic”的接口。
# user.py
import fancy
@fancy
def func(...):
...
目前,提供这种风格的接口需要在运行时修改模块对象以使其可调用。
这通常通过用可调用替代项(例如函数或类实例)替换sys.modules
中的模块对象来完成。
# fancy.py
def fancy(...):
...
sys.modules[__name__] = fancy
这会使原始模块在没有作者进一步的钩子的情况下实际上无法访问,即使使用from module import member
。它还会导致一个“模块”对象,该对象缺少所有特殊的模块属性,包括__doc__
、__package__
、__path__
等。
或者,模块作者可以选择使用提供可调用接口的自定义类型覆盖模块的__class__
属性。
# fancy.py
def fancy(...):
...
class FancyModule(types.ModuleType):
def __call__(self, ...):
return fancy(...)
sys.modules[__name__].__class__ = FancyModule
这两种方法的缺点不仅在于会导致额外的样板代码,还会导致类型检查器失败,因为它们在运行时无法识别模块是可调用的。
$ mypy user.py
user.py:3: error: Module not callable [operator]
Found 1 error in 1 file (checked 1 source file)
规范
当调用模块对象时,如果找到__call__
对象(作为__getattr__
或__dict__
查找的结果),则将使用给定的参数调用该对象。
如果未找到__call__
对象,则将引发TypeError
,这与现有行为一致。
所有这些示例都将被视为有效的可调用模块。
# hello.py
def __call__(...):
pass
# hello.py
class Hello:
pass
__call__ = Hello
# hello.py
def hello():
pass
def __getattr__(name):
if name == "__call__":
return hello
通常应该首选前两种样式,因为它允许类型检查器等工具更轻松地进行静态分析,尽管为了使实现更一致,将允许第三种形式。
目的是允许将任意可调用对象分配给模块的__call__
属性或由模块的__getattr__
方法返回,使模块作者能够选择最合适的机制来使其模块可供用户调用。
向后兼容性和对性能的影响
预计此PEP不会导致任何向后不兼容性。任何已经包含__call__
对象的模块将继续像以前一样工作,但具有直接调用的额外功能。认为具有现有__call__
对象的模块不太可能依赖于调用时引发TypeError
的现有行为。
此PEP的性能影响最小,因为它定义了一个新接口。调用模块将触发在模块对象上查找名称__call__
。创建可调用模块的现有解决方法已经依赖于此通用对象的此行为,从而导致这些可调用模块的性能相似。
类型检查器可能需要相应地更新,以将具有__call__
对象的模块视为可调用。在检查针对不支持可调用模块的旧版 Python 版本的代码时,类型检查器应该可以支持这一点,并期望这些模块也包含前面提到的解决方法之一以使模块可调用。
如何教授这个
对可调用类型的文档将在列表中包含模块,并链接到__call__()
。模拟可调用对象文档将包含一个涵盖可调用模块的部分,其中包含示例代码,类似于自定义模块属性访问的部分。
参考实现
可调用模块的提议实现可在CPython PR #103742中找到。
被拒绝的想法
鉴于引入了__getattr__
和__dir__
,以及启用__call__
使用的提议,我们考虑是否值得允许对模块使用所有特殊方法名称,例如__or__
和__iter__
。虽然这并非完全不受欢迎,但它增加了向后兼容性问题的可能性,并且与__call__
相比,这些其他特殊方法可能对库作者提供的实用程序较少。
版权
本文档置于公共领域或根据CC0-1.0-Universal许可证,以较宽松者为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0713.rst