Mypy 在访问超类属性时给出错误的类型
Mypy give incorrect type when accessing superclass attribute
我正在尝试设置超类属性并在子类中访问它,如下所示:
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?
Mypy 实际上在这里给出了正确的答案。协变可变属性无效,因此 D._store
的类型实际上是 A
。这可能听起来像是一堆理论上的废话,所以我会更详细地解释它:
您声明每个 D
个实例 is-a C
个实例。 C
有一个名为 _store
的可变属性 A
。因此,在任何时候将 _store
属性设置为 A
类型的任何值都是合法的。这意味着 D
实例的任何使用都必须能够处理这样一个事实,即它的 _store
可能是 A
,而不是 B
。这意味着 D._store
的类型是 A
.
如果您的 C._store
实际上是可变的,那么这就是您代码中的真正错误。任何时候你想在 D._store
上使用 B
方法,你需要用 isinstance
或 try
/except
进行测试,之后你当然可以安全地依赖于值是 B
的事实(即使所有者不是 D
也可以工作)。
如果,另一方面,你的 C._store
是不可变的,你想做的是有效的(尽管你真的应该使用 __new__
而不是 __init__
来设置它在那种情况下),但由于当前版本的 Mypy 的限制(或者更确切地说,在 Python 的静态类型系统中,它的表现力不如动态类型系统,在某些方面为了简单起见,有意地,在其他方面只是因为还没有考虑到所有事情),您不能明确指定该事实。而且我不确定它是否会在一般情况下被修复(尽管像 dataclass
不可变属性这样的特定情况可能会在某个时候被修复)。
一个选项是将属性包装在 (read-only) @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
,它会生效。
我正在尝试设置超类属性并在子类中访问它,如下所示:
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?
Mypy 实际上在这里给出了正确的答案。协变可变属性无效,因此 D._store
的类型实际上是 A
。这可能听起来像是一堆理论上的废话,所以我会更详细地解释它:
您声明每个 D
个实例 is-a C
个实例。 C
有一个名为 _store
的可变属性 A
。因此,在任何时候将 _store
属性设置为 A
类型的任何值都是合法的。这意味着 D
实例的任何使用都必须能够处理这样一个事实,即它的 _store
可能是 A
,而不是 B
。这意味着 D._store
的类型是 A
.
如果您的 C._store
实际上是可变的,那么这就是您代码中的真正错误。任何时候你想在 D._store
上使用 B
方法,你需要用 isinstance
或 try
/except
进行测试,之后你当然可以安全地依赖于值是 B
的事实(即使所有者不是 D
也可以工作)。
如果,另一方面,你的 C._store
是不可变的,你想做的是有效的(尽管你真的应该使用 __new__
而不是 __init__
来设置它在那种情况下),但由于当前版本的 Mypy 的限制(或者更确切地说,在 Python 的静态类型系统中,它的表现力不如动态类型系统,在某些方面为了简单起见,有意地,在其他方面只是因为还没有考虑到所有事情),您不能明确指定该事实。而且我不确定它是否会在一般情况下被修复(尽管像 dataclass
不可变属性这样的特定情况可能会在某个时候被修复)。
一个选项是将属性包装在 (read-only) @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
,它会生效。