请原谅我的困惑--我是个使用打字机的新手,并试图将它与以下内容一起使用 mypy
为检查.看来我的问题question似乎发生在人们开始使用打字和Mypy相当多。
我试图定义一个抽象的数据类组合,它将被子类化到具体的类中以增加额外的数据。
所以,在一个简化的形式下,我试图做以下的事情。
from dataclasses import dataclass
from typing import List
@dataclass
class TestResultImage:
base_var_a: int
@dataclass
class TestSeries:
imgs: List[TestResultImage]
# --- concrete instances -------
@dataclass
class SpecificImageType1(TestResultImage):
specific_var_b: float
specific_var_c: int
@dataclass
class SpecificSeries(TestSeries):
imgs: List[SpecificImageType1]
Mypy在上面的操作中失败了,出现了错误
error: Incompatible types in assignment (expression has type "List[SpecificImageType1]", base class "TestSeries" defined the type as "List[TestResultImage]")
note: "List" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance
note: Consider using "Sequence" instead, which is covariant
将{List}改为{Sequence}可以解决这个问题--如错误中所述。
我看到不少SO和Mypy的git问题与这个问题有关,也看到了大家的困惑。
所以我就去尝试阅读了尽可能多的Mypy文档。
但它仍然是--我认为--相当混乱的,为什么 List
是有问题的,当你进行子类化时。 ....或者可能搞不清楚为什么 "List是不变的,但Sequence是共变的"。
所以我想问一下,也许是代表其他像我这样试图真正使用类型化的人,所以Mypy,为了更多琐碎的例子--是否有任何关于List有问题的原因的好解释,以及一些例子?
假设我们在你的原始代码中加入以下内容。
def check_specific_images(imgs: List[SpecificImageType1]) -> None:
for img in imgs:
print(img.specific_var_b)
def modify_series(series: TestSeries) -> None:
series.append(TestResultImage(1))
specific = SpecificTestSeries(imgs=[
SpecificImageType1(1, 2.0, 3),
SpecificImageType1(4, 5.0, 6),
])
modify_series(specific)
check_specific_images(specific.imgs)
这个程序从表面上看应该进行类型检查 specific
是TestSeries的一个实例,所以它是合法的。modify_series(specific)
. 同样地: specific.imgs
是List[SpecificImageType1]类型的,所以在做 check_specific_images(specific.imgs)
也是合法的。
然而,如果我们真正尝试运行这个程序,当我们调用 check_specific_images
! The modify_series
在我们的List[SpecificImageType1]中添加了一个TestResultImage对象,导致后续调用了 check_specific_images
在运行时崩溃。
这个问题从根本上讲是mypy(或者几乎所有其他正常的类型系统)不会让List[SpecificImageType1]被当作List[TestResultImage]的子类型的原因。为了让一个类型成为另一个类型的有效子类型,应该可以在任何期望父类型的位置安全地使用子类型。对于列表来说,这根本不是真的。
为什么这么说呢?因为列表支持 写操作. 在List[TestResultImage]中插入一个TestResultImage(或TestResultImage的任何子类型)应该总是安全的,而对于List[SpecificImageType1]来说,这并不是真的。
所以,如果问题是列表是可变的,那么如果我们使用一个不可变的类型来代替--只支持读操作呢?这将让我们完全避开这个问题。
这正是Sequence:它是一个包含了列表支持的所有只读方法的类型(并且是List的超级类型)。
更广义的说,我们假设我们有某种通用类型 Wrapper[T]以及两个类 Parent 和 Child,其中 Child 是 Parent 的一个子类型。
这就提出了一个问题:Wrapper[Parent]与Wrapper[Child]的关系如何?
对此,有四种可能的答案。
Wrapper是 共变: Wrapper[Child]是Wrapper[Parent]的一个子类型。
Wrapper是 违变: Wrapper[Parent]是Wrapper[Child]的一个子类型。
Wrapper是 不变性: Wrapper[Parent]和Wrapper[Child]互不相关,都不是对方的子类型。
Wrapper是 双变量: Wrapper[Parent]是Wrapper[Child]的一个子类型。和 Wrapper[Child]是Wrapper[Parent]的一个子类型。
当你定义Wrapper[T]时,mypy会让你选择你想要的类型是共变、反变还是不变。一旦你做出了选择,mypy就会执行以下规则。
Mypy不允许你创建双变量类型:只有当这样的类型是安全的时候,如果它支持 既不 也不对T进行读写操作--这将是非常没有意义的。
你通常只在编程语言类型系统中看到双变量类型,这些系统有意想让类型尽可能简单,即使这意味着让用户在他们的程序中引入像上图所示的错误。
这里的高级直觉是,支持对T的读操作或写操作都会对Wrapper[Parent]与Wrapper[Child]的关系进行约束--如果你同时支持这两种操作,综合约束最终会使这两种类型根本不相关。