我们都知道里氏替换原理。该类型可以被其子类型替换而不会破坏它。但是它们的泛型类型 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]。但是如何造成这种情况呢?我将在下面的示例代码中进行解释。

  1. 不变,如果两种情况都不适合。 C[A] 不能与 C[B] 交换,反之亦然。

将概念与 Sink / Source 相结合。

根据他们的行为,我们定义了两种对象。

  1. Source[T]:它产生 T,与 T 是协变的。

  2. 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")

进入全屏模式 退出全屏模式

首先,我们有BaseDerived.``DerivedBase,固有的,所以我们有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()。然而,假设contravariantcovariant.相反是很常见的

我认为在大多数情况下,我们不需要立即添加contravariantcovariant。只有当检查器抱怨时,我们才会仔细观察这些泛型类型之间的关系,如果使用得当。如果是,那么我们考虑添加这些提示。

Logo

Python社区为您提供最前沿的新闻资讯和知识内容

更多推荐