嵌套协议的使用(协议的成员也是协议)

ore*_*uro 7 python-3.x mypy python-typing pyright

考虑一个也用协议注释的 Python 协议属性。我发现在这种情况下,即使我的自定义数据类型遵循嵌套协议,mypy 和 Pyright 都会报告错误。例如,下面的代码Outer遵循协议,因为HasHasA它遵循协议。hasa: HasAInnerHasA

from dataclasses import dataclass
from typing import Protocol

class HasA(Protocol):
    a: int

class HasHasA(Protocol):
    hasa: HasA

@dataclass
class Inner:
    a: int

@dataclass
class Outer:
    hasa: Inner

def func(b: HasHasA): ...

o = Outer(Inner(0))
func(o)
Run Code Online (Sandbox Code Playgroud)

但是,mypy 显示以下错误。

nested_protocol.py:22: error: Argument 1 to "func" has incompatible type "Outer"; expected "HasHasA"  [arg-type]
nested_protocol.py:22: note: Following member(s) of "Outer" have conflicts:
nested_protocol.py:22: note:     hasa: expected "HasA", got "Inner"
Run Code Online (Sandbox Code Playgroud)

我的代码有什么问题吗?

dro*_*oze 6

GitHub 上有一个问题与您的示例几乎完全相同。我认为文档中的激励案例mypy很好地解释了为什么这是非法的。将结构类比到您的示例中,让我们填写一个实现funcInner稍微调整一下:

def func(b: HasHasA) -> None:
    b.hasa.a += 100 - 100

@dataclass
class Inner:
    a: bool

o = Outer(Inner(bool(0)))
func(o)
if o.hasa.a is False:
    print("Oh no! This is still False!")
else:
    print("This is true now!")
Run Code Online (Sandbox Code Playgroud)

这当然是一个人为的示例,但它表明,如果类型检查器没有警告您注意这一点,则内部协议可以扩展内部类型并执行值突变,并且您可以默默地执行类型不安全的操作。

正如文档所建议的mypy,解决方案是将外部协议的变量设置为只读:

class HasHasA(Protocol):
    @property
    def hasa(self) -> HasA:
        ...
Run Code Online (Sandbox Code Playgroud)