在父 class 中键入 subclass

Type subclass from inside the parent class

假设我们有以下 class:

from __future__ import annotations

class BaseSwallow:
    # Can't get the ref to `BaseSwallow` at runtime
    DerivedSwallow = NewType('DerivedSwallow', BaseSwallow) 

    def carry_with(self, swallow: DerivedSwallow):
        self.carry()
        swallow.carry()

    def carry(self):
        pass

class AfricanSwallow(BaseSwallow): pass

godOfSwallows = BaseSwallow()
africanSwallow = AfricanSwallow()

africanSwallow.carry_with(godOfSwallows)  # Should fail at static typing

我想强制要求 carry_with 只能用 classes 派生自 BaseSwallow 的实例调用,所以我使用 NewType 来做到这一点 like the doc says

但是,NewType 需要引用基础 class 对象才能工作,而我在运行时无权访问它。在运行前,由于 annotations 模块,我有 "access" 到 BaseSwallow 但当 运行.

时它仍然会失败

我知道在大多数情况下,为 BaseSwallow 使用抽象基础 Class 是最好的做法,但我不能那样做 for various reasons

有什么想法吗?

您可以将 carry 标记为 abstractmethod 吗?例如,像这样的东西:

import abc

class BaseSwallow:
    def carry_with(self, swallow: BaseSwallow) -> None:
        self.carry()
        swallow.carry()

    @abc.abstractmethod
    def carry(self) -> None:
        raise NotImplementedError('implementation of carry needed')

derived classes 然后可以实现该方法,但是因为基础 class 没有尝试实例化它会导致类型检查失败,表明 BaseSwallow 是一个抽象class 由于缺少属性 carry

我不认为有一种方法可以使用类型注释来表达“T 的所有子 classes,不包括 T”。如果你有一组固定的 subclasses,你可以使用 Union 类型来捕获它们,但这可能不是你想要的。我认为 是您最好的选择:只需使用 BaseSwallow 基础 class 而不是制作复杂的类型来排除基础 class 本身。

此外,我认为您误解了 NewType 的用法。 NewType 用于创建需要显式转换的类型的别名。例如:

URL = NewType('URL', str)

def download(url: URL): ...

link_str = "https://..."  # inferred type is `str`
link_url = URL(link_str)  # inferred type is `URL`
download(link_str)  # type error
download(link_url)  # correct

编辑: 如果您不介意一点开销,您可以通过额外的继承级别来实现。创建 BaseSwallow 的子类型(为方便起见命名为 Swallow),并让所有派生的 class 继承 Swallow 而不是 BaseSwallow。这样,您可以使用 Swallow 类型注释 carry_with 方法:

class BaseSwallow:
    def carry_with(self, swallow: 'Swallow'):  # string as forward reference
        self.carry()
        swallow.carry()

    def carry(self):
        pass

class Swallow(BaseSwallow): pass  # dummy class to serve as base

class AfricanSwallow(Swallow): pass

godOfSwallows = BaseSwallow()
africanSwallow = AfricanSwallow()

africanSwallow.carry_with(godOfSwallows)  # mypy warns about incompatible types