Python 类型提示:逆变、协变、不变
我们都知道里氏替换原理。该类型可以被其子类型替换而不会破坏它。但是它们的泛型类型 C[subtype] 和 C[type] 的关系如何呢?
那么,什么是协变?
如果 A<: B,你可以在任何地方用 A 替换 B
我们说 C[T] 其中 T 绑定到 B 是
1.逆变,当A <: B => C[A] :> C[B]
我们可以在任何地方用 C[A] 替换 C[B]。
2.协变,当A <: B => C[A] <: C[B]
我们可以在任何地方用 C[B] 替换 C[A]。但是如何造成这种情况呢?我将在下面的示例代码中进行解释。
- 不变,如果两种情况都不适合。 C[A] 不能与 C[B] 交换,反之亦然。
将概念与 Sink / Source 相结合。
根据他们的行为,我们定义了两种对象。
-
Source[T]:它产生 T,与 T 是协变的。
-
Sink[T]:它消耗 T,与 T 逆变。
它非常抽象,所以我们直接转到下面的代码。尝试根据评论修改进行实验。
import abc
from typing import Generic, TypeVar
class Base:
def foo(self):
print("foo")
class Derived(Base):
def bar(self):
print("bar")
进入全屏模式 退出全屏模式
首先,我们有Base和Derived.``Derived是Base,固有的,所以我们有Derived <: Base.
T_co = TypeVar('T_co', bound='Base', covariant=True)
class Source(Generic[T_co]):
@abc.abstractmethod
def generate(self) -> T_co: # Produce T_co!
pass
class SourceBase(Source[Base]):
def generate(self) -> Derived: # Produce T_co!
return Derived()
class SourceDerived(Source[Derived]):
def generate(self) -> Derived:
return Derived()
source: Source[Base] = SourceDerived()
source.generate()
#Try to uncomment lines below.
#source_derived: Source[Derived] = SourceBase()
#source_derived.generate()
进入全屏模式 退出全屏模式
现在,我们有了SourceDerived <: SourceBase。如果我们删除covariant=True,我们会得到这个警告:
[Pyright reportGeneralTypeIssues] [E] 类型的表达式 >“SourceDerived”不能分配给声明的类型“Source[Base]”
TypeVar "T_co@Source" 是不变的
“Derived”与“Base”不兼容
如果将covariant修改为contravariant,,就会发生这种情况。covariant告诉检查器 SourceDerived 可以在我们使用 SourceBase 的任何地方安全使用。
def generate(self) -> T_co: <- warining
pass
进入全屏模式 退出全屏模式
警告:[Pyright reportGeneralTypeIssues] [E] 逆变类型变量不能用于返回类型
TypeVar "T_co@Source" 是逆变的
看起来像covariant不仅要检查你使用C[T_co]的地方,还要检查C[T_co]中的方法返回T_co。
接下来,我们看一下contravariant的例子。
T_contra = TypeVar('T_contra', bound='Base', contravariant=True)
class Sink(Generic[T_contra]):
@abc.abstractmethod
def consume(self, value: T_contra):
pass
class SinkBase(Sink[Base]):
def consume(self, value: Base):
value.foo()
class SinkDerived(Sink[Derived]):
def consume(self, value: Derived):
value.bar()
def other_func(self):
pass
base = Base()
derived = Derived()
sink_derived: Sink[Derived] = SinkBase()
#we can safely consumer
sink_derived.consume(base)
sink_derived.consume(derived)
#Try to uncomment this line.
#sink_derived.other_func()
进入全屏模式 退出全屏模式
我们这里有SinkDerive <: SinkBase。删除contravariant=True将收到警告:
[Pyright reportGeneralTypeIssues] [E] 类型“SinkBase”的表达式不能分配给声明的类型 >“Sink[Derived]”
TypeVar "T_contra@Sink" 是不变的
“Base”与“Derived”不兼容
contravariant=True告诉静态检查器 Base 可以安全地使用 Base 或 Derive 类型。虽然我们已经注解了 T_contra 是逆变的,但是如果我们调用 Sink[Derived] 的方法,当然会报错,例如sink_derived.other_func()。然而,假设contravariant与covariant.相反是很常见的
我认为在大多数情况下,我们不需要立即添加contravariant或covariant。只有当检查器抱怨时,我们才会仔细观察这些泛型类型之间的关系,如果使用得当。如果是,那么我们考虑添加这些提示。
更多推荐

所有评论(0)