Python Callable 的等效协议是什么?

han*_*ans 6 python mypy python-typing

我总是认为 Callable 相当于拥有 dunder__call__但显然也有__name__,因为以下代码是正确的mypy --strict

def print_name(f: Callable[..., Any]) -> None:
    print(f.__name__)

def foo() -> None:
    pass

print_name(foo)

print_name(lambda x: x)

Run Code Online (Sandbox Code Playgroud)

python Callable 的实际接口是什么?

我挖出了什么functools.wraps。AFAIU 设定('__module__', '__name__', '__qualname__', '__doc__', '__annotations__')- 这与Callable预期的相同吗?

Dan*_*erg 3

所以mypy到目前为止的情况似乎是,大多数时候,当变量用 注释时Callable,用户期望它代表用户定义的函数(即def something(...): ...)。

尽管用户定义的函数在技术上是可调用函数的子类型,并且即使它们定义了您提到的许多属性,但一些用户并没有意识到这种区别,并且如果使用mypy类似的代码引发错误,他们会感到惊讶这:

from collections.abc import Callable
from typing import Any

def f(cal: Callable[..., Any]) -> None:
    print(cal.__name__)
    print(cal.__globals__)
    print(cal.__kwdefaults__)
    print(cal.foo)
Run Code Online (Sandbox Code Playgroud)

这些print行中的每一行都应该是一个错误,但只有最后一行实际上触发了一个错误。

此外,如果我们定义一个Callable具有这些属性的最小可调用类, Python 和都会将其视为 的子类型mypy,从而产生逻辑矛盾:

class Bar:
    def __call__(self) -> None:
        print(f"hi mom")


f(Bar())  # this is valid
print(Bar().__name__)  # this is an error
Run Code Online (Sandbox Code Playgroud)

到目前为止,他们的论点相当于为那些迄今为止未能看到可调用子类型之间区别的用户提供便利,并通过扩展避免这些用户打开令人困惑的问题,询问为什么可调用对象不应该具有或__name__那些其他属性。(我希望我的解释足够仁慈。)

我发现这是一个非常奇怪的立场(温和地说),我在为此提出的问题中表达了同样的观点。如果在围绕该问题的讨论中达成任何新的见解,我将随时更新此答案。


底线是:你是对的,可调用对象必须具有该__call__方法并且不需要任何其他内容。