如何将自定义协议与 Callable 协议结合起来?

Ana*_*and 11 python type-hinting python-typing

我有一个装饰器,它接受一个函数并返回具有一些附加属性的相同函数:

import functools
from typing import *


def decorator(func: Callable) -> Callable:
    func.attr1 = "spam"
    func.attr2 = "eggs"
    return func
Run Code Online (Sandbox Code Playgroud)

如何键入提示的返回值decorator?我希望类型提示传达两条信息:

  1. 返回值是一个 Callable
  2. 返回值具有属性attr1attr2

如果我写一个协议,

class CallableWithAttrs(Protocol):
    attr1: str
    attr2: str
Run Code Online (Sandbox Code Playgroud)

那我输了Callable。显然我不能让协议继承自Callable;

class CallableWithAttrs(Callable, Protocol):
    attr1: str
    attr2: str
Run Code Online (Sandbox Code Playgroud)

mypy 说:

error: Invalid base class "Callable"
Run Code Online (Sandbox Code Playgroud)

另一方面,如果我只使用Callable,我会丢失有关添加属性的信息。



这在引入类型变量时可能更加复杂,即当装饰器必须返回与给定函数相同类型的可调用对象时func,正如 MisterMiyagi 在评论中指出的那样。

import functools
from typing import *

C = TypeVar('C', bound=Callable)


def decorator(func: C) -> C:
    func.attr1 = "spam"
    func.attr2 = "eggs"
    return func
Run Code Online (Sandbox Code Playgroud)

现在我该怎么办?我不能从类型变量继承:

class CallableWithAttrs(C, Protocol):
    attr1: str
    attr2: str
Run Code Online (Sandbox Code Playgroud)
error: Invalid base class "C"
Run Code Online (Sandbox Code Playgroud)

a_g*_*est 7

由于typing.Callable对应于collections.abc.Callable,您可以定义一个Protocol实现__call__

class CallableWithAttrs(Protocol):
    attr1: str
    attr2: str

    def __call__(self, *args, **kwargs): pass
Run Code Online (Sandbox Code Playgroud)


Mis*_*agi 6

人们可以参数化一个ProtocolCallable

from typing import Callable, TypeVar, Protocol

C = TypeVar('C', bound=Callable)  # placeholder for any Callable


class CallableObj(Protocol[C]):   # Protocol is parameterised by Callable C ...
    attr1: str
    attr2: str

    __call__: C                   # ... which defines the signature of the protocol
Run Code Online (Sandbox Code Playgroud)

这会创建Protocol自身与任意的交集Callable


C因此,接受任何可调用对象的函数可以返回CallableObj[C]具有所需属性的相同签名的可调用对象:

def decorator(func: C) -> CallableObj[C]: ...
Run Code Online (Sandbox Code Playgroud)

MyPy 正确识别签名和属性:

def dummy(arg: str) -> int: ...

reveal_type(decorator(dummy))           # CallableObj[def (arg: builtins.str) -> builtins.int]'
reveal_type(decorator(dummy)('Hello'))  # int
reveal_type(decorator(dummy).attr1)     # str
decorator(dummy)(b'Fail')  # error: Argument 1 to "dummy" has incompatible type "bytes"; expected "str"
decorator(dummy).attr3     # error: "CallableObj[Callable[[str], int]]" has no attribute "attr3"; maybe "attr2"?
Run Code Online (Sandbox Code Playgroud)