在子类中键入 return 值的提示

Type hint for return value in subclass

我正在编写一个 CustomEnum class,我想在其中添加一些辅助方法,然后 classes subclassing 我的 CustomEnum 可以使用这些方法。其中一种方法是 return 一个随机枚举值,这就是我被卡住的地方。该函数按预期工作,但在类型提示方面,我无法想出一种说法 "the return type is the same type of cls".

我相当确定其中涉及到一些 TypeVar 或类似的魔法,但由于我从来没有使用过它们,所以我从来没有花时间去弄清楚它们。

class CustomEnum(Enum):
    @classmethod
    def random(cls) -> ???:
        return random.choice(list(cls))


class SubclassingEnum(CustomEnum):
    A = "a"
    B = "b"

random_subclassing_enum: SubclassingEnum
random_subclassing_enum = SubclassingEnum.random() # Incompatible types in assignment (expression has type "CustomEnum", variable has type "SubclassingEnum")

有人可以帮助我或提示我如何进行吗?

谢谢!

这里的语法有点糟糕,但我认为没有更简洁的方法来执行此操作。以下passes MyPy

from typing import TypeVar
from enum import Enum
import random 

T = TypeVar("T", bound="CustomEnum")

class CustomEnum(Enum):
    @classmethod
    def random(cls: type[T]) -> T:
        return random.choice(list(cls))

(在 python 版本 <= 3.8 中,如果要对其进行参数化,则必须使用 typing.Type 而不是内置 type。)

这是怎么回事?

T 在顶部定义为类型变量,即 CustomEnum class 的“bound”。这意味着用 T 注释的变量只能是 CustomEnum 的实例或继承自 CustomEnum.

的 class 的实例

在上面的 class 方法中,我们实际上是使用这个类型变量来定义 cls 参数相对于 return 类型的类型。通常我们做相反的事情——我们通常根据函数输入参数的类型定义函数的 return 类型。因此,如果这感觉有点令人费解,这是可以理解的!

我们说:这个方法会导致 class 的实例——我们不知道 class 会是什么,但我们知道它要么是 CustomEnum或者 class 继承自 CustomEnum。我们还知道,无论 class 是 returned,我们可以保证函数中 cls 参数的类型在类型层次结构中从return 值。

在很多情况下,我们可能知道type[cls]永远是一个固定值。在那些情况下,可以将其硬编码到类型注释中。但是,最好 [=7​​7=] 不要 这样做,而是使用这种方法,它清楚地显示了输入类型与 return 类型之间的关系(即使它使用这样做的语法太糟糕了!)。

Further reading: the MyPy documentation on the type of class objects.

进一步的解释和示例

对于绝大多数classes(不包括Enumes,他们使用metaclasses,但让我们暂时搁置),以下将成立真:

示例 1

Class A:
    pass

instance_of_a = A()
type(instance_of_a) == A # True
type(A) == type # True

示例 2

class B:
    pass

instance_of_b = B()
type(instance_of_b) == B # True
type(B) == type # True

对于 CustomEnum.random() 方法的 cls 参数,我们在我的 示例 1 中注释了等同于 A 而不是 instance_of_a 以上。

  • instance_of_a的类型是A
  • 但是 A 的类型不是 AA 是 class,不是 class.[=96= 的实例]
  • 类 不是 classes 的实例;它们要么是 type 的实例,要么是继承自 type.
  • 的自定义元 classes 的实例
  • 这里没有使用元class;因此,A 的类型是 type.

规则如下:

  • 所有 python class 实例的类型 将是 class 它们的实例。
  • 所有 python classes 的类型将是 type 或(如果你对自己太聪明的话好)一个自定义元 class 继承自 type.

使用您的 CustomEnum class,我们 可以 使用元 class 注释 cls 参数 enum 模块使用 (enum.EnumType, if you want to know)。但是,正如我所说 - 最好不要。我建议的解决方案更清楚地说明了输入类型和 return 类型之间的关系。