如何在python中使用TypeVar进行多个通用协议的输入和输出?

Luk*_*les 4 python python-typing

我想使用多个通用协议并确保它们兼容:

from typing import TypeVar, Protocol, Generic
from dataclasses import dataclass

# checking fails as below and with contravariant=True or covariant=True:
A = TypeVar("A") 

class C(Protocol[A]):
    def f(self, a: A) -> None: pass

class D(Protocol[A]):
    def g(self) -> A: pass

# Just demonstrates my use case; doesn't have errors:
@dataclass
class CompatibleThings(Generic[A]):
    c: C[A]
    d: D[A]
Run Code Online (Sandbox Code Playgroud)

Mypy 出现以下错误:

Invariant type variable 'A' used in protocol where contravariant one is expected
Invariant type variable 'A' used in protocol where covariant one is expected
Run Code Online (Sandbox Code Playgroud)

C我知道这可以通过创建通用ABC 类来完成D,但我想使用协议。

Ada*_*ern 8

简而言之,您的方法破坏了子类型的传递性;有关详细信息,请参阅PEP 544 的这一部分。它非常清楚地解释了为什么您的D协议(以及隐含的C协议)会遇到这个问题,以及为什么每个协议需要不同类型的方差来解决它。您还可以在维基百科上查找有关类型差异的信息。

解决方法如下:使用协变和逆变协议,但使通用数据类保持不变。这里最大的障碍是继承,你必须处理它才能使用协议,但这与你的目标相切。我将在这里切换命名以突出显示正在发挥作用的继承,这就是全部内容:

A = TypeVar("A") # Invariant type
A_cov = TypeVar("A_cov", covariant=True) # Covariant type
A_contra = TypeVar("A_contra", contravariant=True) # Contravariant type

# Give Intake its contravariance
class Intake(Protocol[A_contra]):
    def f(self, a: A_contra) -> None: pass

# Give Output its covariance
class Output(Protocol[A_cov]):
    def g(self) -> A_cov: pass

# Just tell IntakeOutput that the type needs to be the same
# Since a is invariant, it doesn't care that
# Intake and Output require contra / covariance
@dataclass
class IntakeOutput(Generic[A]):
    intake: Intake[A]
    output: Output[A]
Run Code Online (Sandbox Code Playgroud)

您可以看到这适用于以下测试:

class Animal:
    ...
    
class Cat(Animal):
    ...
    
class Dog(Animal):
    ...
    
class IntakeCat:
    def f(self, a: Cat) -> None: pass

class IntakeDog:
    def f(self, a: Dog) -> None: pass

class OutputCat:
    def g(self) -> Cat: pass

class OutputDog:
    def g(self) -> Dog: pass

compat_cat: IntakeOutput[Cat] = IntakeOutput(IntakeCat(), OutputCat())
compat_dog: IntakeOutput[Dog] = IntakeOutput(IntakeDog(), OutputDog())

# This is gonna error in mypy
compat_fail: IntakeOutput[Dog] = IntakeOutput(IntakeDog(), OutputCat())
Run Code Online (Sandbox Code Playgroud)

这给出了以下错误:

main.py:48: error: Argument 2 to "IntakeOutput" has incompatible type "OutputCat"; expected "Output[Dog]"
main.py:48: note: Following member(s) of "OutputCat" have conflicts:
main.py:48: note:     Expected:
main.py:48: note:         def g(self) -> Dog
main.py:48: note:     Got:
main.py:48: note:         def g(self) -> Cat
Run Code Online (Sandbox Code Playgroud)

那么有什么问题呢?你要放弃什么?即,继承IntakeOutput。以下是你不能做的事情:

class IntakeAnimal:
    def f(self, a: Animal) -> None: pass

class OutputAnimal:
    def g(self) -> Animal: pass

# Ok, as expected
ok1: IntakeOutput[Animal] = IntakeOutput(IntakeAnimal(), OutputAnimal())

# Ok, because Output is covariant
ok2: IntakeOutput[Animal] = IntakeOutput(IntakeAnimal(), OutputDog())

# Both fail, because Intake is contravariant
fails1: IntakeOutput[Animal] = IntakeOutput(IntakeDog(), OutputDog())
fails2: IntakeOutput[Animal] = IntakeOutput(IntakeDog(), OutputAnimal())

# Ok, because Intake is contravariant
ok3: IntakeOutput[Dog] = IntakeOutput(IntakeAnimal(), OutputDog())

# This fails, because Output is covariant
fails3: IntakeOutput[Dog] = IntakeOutput(IntakeAnimal(), OutputAnimal())
fails4: IntakeOutput[Dog] = IntakeOutput(IntakeDog(), OutputAnimal())
Run Code Online (Sandbox Code Playgroud)

所以。就在那里。您可以在这里进行更多尝试。