Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python增强提案

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__相比,这些其他特殊方法可能对库作者提供的实用程序较少。


来源:https://github.com/python/peps/blob/main/peps/pep-0713.rst

上次修改:2023-09-09 17:39:29 GMT