我正在尝试设置超类属性并在子类中访问它,如下所示:
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?
答案 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
方法,则需要使用isinstance
或try
/ except
进行测试,之后您可以安全地进行测试依赖于该值为B
的事实(即使所有者不是D
也会有效。)
另一方面,如果您的C._store
是不可变的,那么您想要做的是有效的(尽管您确实应该使用__new__
而不是__init__
来设置它case),但是由于当前版本的Mypy的限制(或者更确切地说,在Python的静态类型系统中,它比动态类型系统更不具有表现力,在某些方面有意为简单起见,在其他方面仅仅因为没有一切都经过深思熟虑,你无法明确地指明这个事实。而且我不确定它是否会被修复(尽管特定情况如dataclass
不可变属性可能会在某些时候出现)。
一个选项是将属性包装在(只读)@property
,as 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
,然后进行验证。