【问题标题】:Why does mypy report incompatible types when child and parent class both satisfy grandparent type definition?当子类和父类都满足祖父类型定义时,为什么 mypy 会报告不兼容的类型?
【发布时间】:2019-10-15 01:33:27
【问题描述】:

给定以下代码:

from typing import Tuple


class Grandparent:
    items: Tuple[str, ...] = ()


class Parent(Grandparent):
    items = ('foo',)


class Child(Parent):
    items = ('foo', 'bar')

mypy 报如下错误:

error: Incompatible types in assignment (expression has type "Tuple[str, str]", base class "Parent" defined the type as "Tuple[str]")

像这样更改代码(在Parent 类中再次指定相同的类型)满足mypy

from typing import Tuple


class Grandparent:
    items: Tuple[str, ...] = ()


class Parent(Grandparent):
    items: Tuple[str, ...] = ('foo',)


class Child(Parent):
    items = ('foo', 'bar')

既然items 在所有位置的分配都满足相同/原始定义,为什么我需要在类层次结构中的多个位置为items 重新指定相同的类型?有没有办法避免需要这样做?

【问题讨论】:

    标签: python mypy python-typing


    【解决方案1】:

    我相信这是 mypy 做出的设计选择。简而言之,你的问题的症结在于:当我们覆盖某个属性时,我们是要使用与父级相同的类型,还是使用新的覆盖类型?

    Mypy 选择了前者——可以说在许多情况下它更直观。例如,如果我有以下类层次结构:

    class Parent:
        def foo(self, p1: int) -> None: ...
    
    class Child(Parent):
        def foo(self, p1: int, p2: str = "bar") -> None: ...
    

    ...Child.foo 具有 def (self: Child, p1: int, p2: str = ...) -> None 的类型而不是直接继承 Parent.foo 的类型(即 def (self: Parent, p1 : int) -> None)是有意义的。

    这样,如果您执行Child().foo(1, "a"),所有内容仍会键入检查。更广泛地说,允许优化父类型很有用,唯一的限制是子类型在优化后仍需要遵循 Liskov 替换原则。

    如果规则是子定义在方法中获胜,那么为了保持一致性,将相同的规则应用于属性是有意义的。

    至于如何解决这个问题——在你的鞋子里,我可能只是满足于继续为每个分配添加类型注释。我不认为这是一个很大的负担。

    或者,我可能会考虑将整个类层次结构折叠成一个单独的类,该类接受适当的元组作为__init__ 中的参数,以尝试避开硬编码的需要。但这对于您尝试做的任何事情都可能不是一个可行的解决方案。

    【讨论】:

    • 这真的很有趣,也很有意义 - 谢谢。在这种情况下,我们调整类层次结构或将items 传递给__init__ 是没有意义的(我遇到的真正的类实际上是包含各种mixin 的Django 管理类,以及@987654330 @ 是像 fields 这样的东西,Django 期望它是类变量,所以我要么不拥有,要么不想像现有的类层次结构那样共享东西)。但只要我知道这是设计使然,而不是因为我错过了什么,添加额外的类型注释就可以了。
    猜你喜欢
    • 1970-01-01
    • 2021-04-14
    • 1970-01-01
    • 2022-06-14
    • 2019-04-09
    • 2016-11-04
    • 2016-09-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多