如何在 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 限制为Aself.signal = Signal[A]() 你基本上在那里设置了T = AB 可能是一个子类,但因为 A.something 不存在,所以您不能使用该属性。您现在将所有内容都绑定到A。接受B 的方法显然可以使用B.something,因此基类A 永远无法满足该要求。 【参考方案1】:

传递给Signal[A]connectorCallable[[A], None] 类型,这意味着它必须保证能够处理A(或其任何子类)的任何实例。 handle_b 无法实现此承诺,因为它仅适用于 B 的实例,因此它不能用作 signal 类型为 Signal[A]connector

据推测,B 的任何实例的signalconnector 只会被要求处理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 中继承类型提示?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Maven POM 中继承构建链?

在 Python 中继承 int

如何在 Swift 中继承 UITableViewController

如何在 TypeScript 中继承 PromiseLike 接口?

如何在一个类中继承 QGraphicsRectItem 和 QGraphicsEllipseItem?

如何在 JavaScript 中继承 C++ 类?