【问题标题】:How to define callable attribute with covariant return type on protocol?如何在协议上定义具有协变返回类型的可调用属性?
【发布时间】:2021-07-30 15:55:47
【问题描述】:

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

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

【问题讨论】:

    标签: python mypy python-typing


    【解决方案1】:

    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 不适合。

    【讨论】:

    • “一种可能的替代方法是用方法替换属性:”好主意。添加@property 使该方法成为只写属性,与用例匹配得更好。
    • 您能否将@property 添加到您的最终代码块中?这是我为我的真实代码选择的变体,它最符合预期——会接受这个解决方案。
    • 当然,补充 :) 这是一个不错的改进
    【解决方案2】:

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

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

    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]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-10-26
      • 1970-01-01
      • 2022-11-27
      • 1970-01-01
      • 2023-04-06
      • 2013-02-06
      • 2017-08-02
      相关资源
      最近更新 更多