微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何在协议上定义具有协变返回类型的可调用属性?

如何解决如何在协议上定义具有协变返回类型的可调用属性?

通常理解为可调用的返回类型是covariant。当使用可调用属性定义类型时,我确实可以使返回类型通用和协变:

from typing import TypeVar,Callable,Generic,Sequence
from dataclasses import dataclass

R = TypeVar("R",covariant=True)

@dataclass
class Works(Generic[R]):
    call: Callable[[],R]  # returns an R *or subtype*

w: Works[Sequence] = Works(lambda: [])  # okay: list is subtype of Sequence

然而,这同样不适用于 Protocol。当我以同样的方式为该类型定义一个 Protocol 时,MyPy 拒绝这样做——它坚持返回类型必须是 invariant。

from typing import TypeVar,Protocol

R = TypeVar("R",covariant=True)

class Fails(Protocol[R]):
    attribute: Callable[[],R]
$ python -m mypy so_testbed.py --pretty
so_testbed.py:5: error: Covariant type variable "R" used in protocol where invariant one is expected
    class Fails(Protocol[R]):
    ^
Found 1 error in 1 file (checked 1 source file)

如何为尊重 Protocol 协方差的具体类型正确定义 R

解决方法

使用 Protocol 显然无法实现您的尝试 - 请参阅 PEP 544 中的以下内容:


可变属性的协变子类型

被拒绝,因为协变 可变属性的子类型化是不安全的。考虑这个例子:

class P(Protocol):
    x: float

def f(arg: P) -> None:
    arg.x = 0.42

class C:
    x: int

c = C()
f(c)  # Would typecheck if covariant subtyping
      # of mutable attributes were allowed.
c.x >> 1  # But this fails at runtime

最初提议出于实际原因允许这样做,但它 随后被拒绝,因为这可能会掩盖一些难以发现的错误。


由于您的 attribute 是可变成员 - 您不能让它与 R 协变。

一种可能的替代方法是用一个方法替换 attribute

class Passes(Protocol[R]):
    @property
    def attribute(self) -> Callable[[],R]:
        pass

它通过了类型检查 - 但它是一个不灵活的解决方案。

如果您需要可变协变成员,则 Protocol 不是最佳选择。

,

正如@Daniel Kleinstein 指出的那样,您不能通过协变变量来参数化协议类型,因为它用于可变属性。

另一种选择是将变量分成两个(协变和不变)并在两个协议中使用它们(replace CallableProtocol)。

from typing import TypeVar,Callable,Protocol

R_cov = TypeVar("R_cov",covariant=True)
R_inv = TypeVar("R_inv")

class CallProto(Protocol[R_cov]):
    def __call__(self) -> R_cov: ...
    
class Fails(Protocol[R_inv]):
    attribute: CallProto[R_inv]

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。