python中多个泛型协议的输入输出如何使用TypeVar?

How to use TypeVar for input and output of multiple generic Protocols in python?

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

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]

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

我知道这可以通过制作 CD 通用 ABC 类 来完成,但我想使用协议。

简短的解释是你的方法破坏了子类型的传递性;有关类型差异的信息,请参阅 this section of PEP 544 for more information. It gives a pretty clear explanation of why your D protocol (and, implicitly, your C protocol) run into this problem, and why it requires different types of variance for each to solve it. You can also look on Wikipedia

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

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]

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

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())

出现以下错误:

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

那么有什么收获呢?你要放弃什么?即,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())

所以。就在那里。你可以多玩玩这个 here.