访问超类属性时,Mypy提供的类型不正确

时间:2018-04-09 17:55:37

标签: python mypy

我正在尝试设置超类属性并在子类中访问它,如下所示:

class A:
    pass

class B(A):
    pass

class C:
    def __init__(self, param: A) -> None:
     self.store = param

class D(C):
    def __init__(self, param: B) -> None:
     super().__init__(param)
     reveal_type(self.store)

当我运行mypy时,我得到“Revealed type is'test.A'”我如何让它在子类中引用B而不是A?

1 个答案:

答案 0 :(得分:1)

Mypy实际上是在这里给出了正确答案。协变可变属性无效,因此D._store的类型实际上是A。这听起来像是一堆理论上的废话,所以我会更详细地解释一下:

您声明每个D实例都是C个实例。 C有一个名为_store的类型为A的可变属性。因此,将_store属性随时设置为A类型的任何值都是合法的。这意味着对D实例的任何使用都必须能够处理其_store可能是A而不是B这一事实。这意味着D._store的类型为A

如果您的C._store实际上是可变的,那么这是您代码中的真正错误。如果您想在B上使用D._store方法,则需要使用isinstancetry / except进行测试,之后您可以安全地进行测试依赖于该值为B的事实(即使所有者不是D也会有效。)

另一方面,如果您的C._store是不可变的,那么您想要做的是有效的(尽管您确实应该使用__new__而不是__init__来设置它case),但是由于当前版本的Mypy的限制(或者更确切地说,在Python的静态类型系统中,它比动态类型系统更不具有表现力,在某些方面有意为简单起见,在其他方面仅仅因为没有一切都经过深思熟虑,你无法明确地指明这个事实。而且我不确定它是否会被修复(尽管特定情况如dataclass不可变属性可能会在某些时候出现)。

一个选项是将属性包装在(只读)@propertyas documented for Protocol members中。但是,这当然会增加您的实现和设计的复杂性。

或者,感谢a bug in Mypy,你可以通过向类型检查器说谎来解决这个问题:

class C:
    _store: A
    def __init__(self, param: A) -> None:
     self._store = param

class D(C):
    _store: B
    def __init__(self, param: B) -> None:
     super().__init__(param)
     reveal_type(self._store)

如果_store确实是不可变的,这将为您提供正确的答案 - 直到修复错误。这可能是现在好的,假设你不会不安全地滥用这个错误,并且可以坚持使用当前版本的Mypy,直到一个版本出来让你正确地写东西。

如果在此之前你不能坚持当前版本的Mypy,你可以随时这样做:

class C:
    _store: A: # type: ignore
    def __init__(self, param: A) -> None:
     self._store = param

class D(C):
    _store: B
    def __init__(self, param: B) -> None:
     super().__init__(param)
     reveal_type(self._store)

但当然这意味着你在_store上根本没有得到任何检查;您可以在其中粘贴int,然后进行验证。