使用 typing 模块和 mypy 的多重继承

Multiple inheritance using typing module and mypy

考虑以下代码:

from typing import Union

class A:
  def function_in_a(self) -> str:
    return 'a'

class B:
  def function_in_b(self) -> str:
    return "b"

class C(A, B):
  pass

def call_functions(c: Union[A, B]) -> None:
  print(c.function_in_a())
  print(c.function_in_b())

if __name__=="__main__":
  c = C()
  call_functions(c)

请注意,函数 call_functions 依赖于 类 AB 中包含的定义。它期望从这两个 类.

继承的对象

此代码将在 运行 使用 python test.py 时编译。但是 mypy --strict test.py 抛出一个错误:

test.py:15: note: Revealed type is "Union[test.A, test.B]"
test.py:16: error: Item "B" of "Union[A, B]" has no attribute "function_in_a"
test.py:17: error: Item "A" of "Union[A, B]" has no attribute "function_in_b"
Found 2 errors in 1 file (checked 1 source file)

这对我来说很有意义。 Union 表示 c 可以是 AB 的子类,但 不能同时是 。我在 PEP483 but a quick perusal of the typing module docs 中看到 Intersection 类型的提及表明这种类型从未实现过。

如何让 mypy 识别 call_functions 的参数是继承自 both A and[=39 的对象=] B 使用类型提示?

使用 typing.Protocol 定义必须实现函数中调用的两种方法的类型。

from typing import Protocol


class A:
    def function_in_a(self) -> str:
        return 'a'


class B:
    def function_in_b(self) -> str:
        return "b"


class C(A, B):
    pass


class D(B):
    pass


class ProtoAB(Protocol):
    def function_in_a(self) -> str:
        ...

    def function_in_b(self) -> str:
        ...


def call_functions(obj: ProtoAB) -> None:
    print(obj.function_in_a())
    print(obj.function_in_b())


def main() -> None:

    c = C()
    call_functions(c)
    d = D()
    call_functions(d)


if __name__ == "__main__":
    main()

另一个解决方案是使ABProtocol。协议可以正常使用 class:

from typing import Protocol
        
class A(Protocol):
    def function_in_a(self) -> str:
        return 'a'
class B(Protocol):
    def function_in_b(self) -> str:
        return "b"
class AB(A, B, Protocol):
    pass
    
def call_functions(c: AB) -> None:
    print(c.function_in_a())
    print(c.function_in_b())

class C(A, B):
    pass
call_functions(C())