看示例代码(mypy_test.py):
import typing
class Base:
def fun(self, a: str):
pass
SomeType = typing.NewType('SomeType', str)
class Derived(Base):
def fun(self, a: SomeType):
pass
现在 mypy 抱怨:
mypy mypy_test.py
mypy_test.py:10: error: Argument 1 of "fun" incompatible with supertype "Base"
在这种情况下,我如何使用类层次结构并保持类型安全?
软件版本:
mypy 0.650 Python 3.7.1
我尝试过的:
import typing
class Base:
def fun(self, a: typing.Type[str]):
pass
SomeType = typing.NewType('SomeType', str)
class Derived(Base):
def fun(self, a: SomeType):
pass
但没有帮助。
一位用户评论道:“看起来你无法缩小覆盖方法中接受的类型的范围?”
但在这种情况下,如果我在基类签名中使用尽可能广泛的类型(
typing.Any
),它也不应该工作。但确实如此:
import typing
class Base:
def fun(self, a: typing.Any):
pass
SomeType = typing.NewType('SomeType', str)
class Derived(Base):
def fun(self, a: SomeType):
pass
mypy 没有对上面的代码进行抱怨。
遇到的 mypy 错误是因为尝试在子类
Derived
中
缩小
fun
方法的参数类型。在类型提示中,子类型只能使参数类型更广泛(协变)或保持不变,而不能使其更狭窄(逆变)。
原因是类型安全。考虑以下代码:
class Base:
def fun(self, a: str):
print(a.upper()) # str 类型的方法
class Derived(Base):
def fun(self, a: SomeType):
print(a.lower()) # SomeType 类型的方法
b: Base = Derived() # 多态性:b 是 Base 类型,但指向 Derived 对象
b.fun("HELLO")
如果允许
Derived.fun
将参数类型缩小为
SomeType
,那么
b.fun("HELLO")
将会产生问题。
Base.fun
期望一个字符串并调用
str.upper()
,但它会收到一个
SomeType
,而
SomeType
可能没有
upper()
方法。
解决方案
-
选项 1:
使
Base.fun
的参数类型更广泛:
```python from typing import Any
class Base: def fun(self, a: Any): pass
SomeType = typing.NewType('SomeType', str)
class Derived(Base): def fun(self, a: SomeType): pass ```
如所见,使用
Any
可以解决问题,因为它可以接受任何类型。但是,这可能会牺牲类型安全性。
- 选项 2: 使用泛型和类型约束:
```python from typing import TypeVar, Generic
T = TypeVar('T', bound=str)
class Base(Generic[T]): def fun(self, a: T): pass
SomeType = typing.NewType('SomeType', str)
class Derived(Base[SomeType]): def fun(self, a: SomeType): pass ```
在这里,我们使用泛型
T
来表示参数类型,并使用
bound=str
限制
T
必须是
str
的子类型。这样,
Derived
可以安全地将
T
指定为
SomeType
。
- 选项 3: 重新考虑的设计。如果需要在子类中大幅度更改方法的参数类型,那么的类层次结构可能需要重新设计。
选择哪种方案取决于的具体需求和代码结构。
标签:python,python-typing,mypy From: 54346721