通常可以理解,可调用的返回类型是 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
?
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
不是正确的选择。
另一种选择是将变量分成两部分(协变和不变)并在两个协议中使用它们(
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]
但是,如果我正在编写 pybind11 项目,当然 C++ 类中的属性类型不会改变。
但是,我未能找到一种方法通过更改我的 pyi 文件来向类型检查器公开这些重要信息。
我尝试编写属性和设置器来定义属性。但 setter 仍然不能将协变类型作为参数。
有没有合适的方法让它发挥作用?
我相信这在理论上是可行的,因为这些绑定类是由模板定义的。将协议编写为 typehint 只是重写该模板。我认为关键点是让类型检查器知道所需的信息