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

问题描述 投票:0回答:3

通常可以理解,可调用的返回类型是 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, Callable, 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

python mypy python-typing
3个回答
3
投票
您所尝试的操作显然无法通过

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

不是正确的选择。


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

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

replaceCallable

 with 
Protocol
)。

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]
    

0
投票
@Daniel Kleinstein 嗯,当然很难让属性的类型无法更改。

但是,如果我正在编写 pybind11 项目,当然 C++ 类中的属性类型不会改变。

但是,我未能找到一种方法通过更改我的 pyi 文件来向类型检查器公开这些重要信息。

我尝试编写属性和设置器来定义属性。但 setter 仍然不能将协变类型作为参数。

有没有合适的方法让它发挥作用?

我相信这在理论上是可行的,因为这些绑定类是由模板定义的。将协议编写为 typehint 只是重写该模板。我认为关键点是让类型检查器知道所需的信息

© www.soinside.com 2019 - 2024. All rights reserved.