如何在 python 中继承类型提示?
Posted
技术标签:
【中文标题】如何在 python 中继承类型提示?【英文标题】:How to have inherited type hints in python? 【发布时间】:2018-06-02 10:30:51 【问题描述】:所以我的问题是,当我有一个 A
类型的类来执行操作并且我将这些函数用作 subclass(B)
时,它们仍然是为类 A
输入的,并且不接受我的类 B
对象作为参数或函数签名。
我的问题简化了:
from typing import TypeVar, Generic, Callable
T = TypeVar('T')
class Signal(Generic[T]):
def connect(self, connector: Callable[[T], None]) -> None:
pass
def emit(self, payload: T):
pass
class A:
def __init__(self) -> None:
self.signal = Signal[A]()
def do(self) -> None:
self.signal.emit(self)
def handle_b(b: "B") -> None:
print(b.something)
class B(A):
def __init__(self) -> None:
super().__init__()
self.signal.connect(handle_b)
@property
def something(self) -> int:
return 42
我也可以提供完整的信号类,但这只会分散问题的注意力。这让我在 mypy 中出现了一个错误:
error: Argument 1 to "connect" of "Signal" has incompatible type Callable[[B], None]; expected Callable[[A], None]
由于信号处理是在A
中实现的,所以子类B
不能期望返回B
类型的对象,即使它显然应该没问题...
【问题讨论】:
嗯,错误是正确的,您确实将self.signal
限制为A
:self.signal = Signal[A]()
。
你基本上在那里设置了T = A
。 B
可能是一个子类,但因为 A.something
不存在,所以您不能使用该属性。您现在将所有内容都绑定到A
。接受B
的方法显然可以使用B.something
,因此基类A
永远无法满足该要求。
【参考方案1】:
传递给Signal[A]
的connector
是Callable[[A], None]
类型,这意味着它必须保证能够处理A
(或其任何子类)的任何实例。 handle_b
无法实现此承诺,因为它仅适用于 B
的实例,因此它不能用作 signal
类型为 Signal[A]
的 connector
。
据推测,B
的任何实例的signal
的connector
只会被要求处理B
的实例,因此它不需要是Signal[A]
类型,但是Signal[B]
就足够了。这意味着signal
的类型不是固定的,而是随着A
的不同子类而变化,这意味着A
需要是通用的。
The answer by ogurets 正确地使A
泛型,但是do
没有问题,因为不清楚self
是否是self.signal.emit
所期望的类型。我们可以保证这些类型将始终通过使用与Signal
相同的类型变量注释self
来匹配。通过使用由A
绑定的新类型变量_A
,我们告诉mypy self
将始终是A
的子类型,因此具有属性signal
。
from __future__ import annotations
from collections.abc import Callable
from typing import TypeVar, Generic
T = TypeVar('T')
class Signal(Generic[T]):
def connect(self, connector: Callable[[T], None]) -> None:
pass
def emit(self, payload: T):
print(payload)
_A = TypeVar('_A', bound='A')
class A(Generic[_A]):
signal: Signal[_A]
def __init__(self) -> None:
self.signal = Signal[_A]()
def do(self) -> None:
self.signal.emit(self)
def handle_b(b: "B") -> None:
print(b.something)
class B(A['B']):
def __init__(self) -> None:
super().__init__()
self.signal.connect(handle_b)
@property
def something(self) -> int:
return 42
b = B()
reveal_type(b.signal) # Revealed type is '...Signal[...B*]'
【讨论】:
【参考方案2】:类型提示错误是完全正确的。您在A
的__init__
方法中创建了一个A
类型为Signal
的实例:
self.signal = Signal[A]()
传入子类没问题,但与Signal
实例交互的所有代码现在已 仅适用于A
实例。另一方面,handle_b()
需要 B
的实例,并且不能将要求降低到 A
。
删除约束:
self.signal = Signal()
或在每个子类中创建一个具有正确类型的实例。
【讨论】:
不应该有办法告诉类型 Hinting 接受 A 的子类型吗?我的意思是禁用打字工作,但为什么有类型提示呢...... 抱歉,暂时离开键盘;可能可以使用标记为协变的TypeVar()
而不是A
,但要到明天才能测试。
我已经尝试过了,我得到了一个问题。py:10: 错误: 不能使用协变类型变量作为参数就行了: def emit(self, payload: T): 而且仍然有旧错误:D 也许升级我的 mypy 版本...【参考方案3】:
from __future__ import annotations
from typing import TypeVar, Generic, Callable
T = TypeVar('T')
class Signal(Generic[T]):
def connect(self, connector: Callable[[T], None]) -> None:
pass
def emit(self, payload: T):
pass
class A(Generic[T]):
def __init__(self) -> None:
self.signal = Signal[T]()
def do(self: A) -> None:
self.signal.emit(self)
def handle_b(b: B) -> None:
print(b.something)
class C:
pass
def handle_c(c: C) -> None:
print(c)
class B(A[B]):
def __init__(self) -> None:
super().__init__()
self.signal.connect(handle_b) # OK
self.signal.connect(handle_c) # incompatible type
@property
def something(self) -> int:
return 42
【讨论】:
好主意,但我仍然在类 A 的发射和发射函数定义本身上有错误,mypy 0.600 嗯.. pip 的 mypy (0.5.0) 的最新版本看起来不错。可悲的是,现在太忙了,无法深入研究。以上是关于如何在 python 中继承类型提示?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Swift 中继承 UITableViewController
如何在 TypeScript 中继承 PromiseLike 接口?