我正在编写一个 CustomEnum 类,我想在其中添加一些辅助方法,然后子类化我的 CustomEnum 的类可以使用这些方法。其中一种方法是返回一个随机枚举值,这就是我陷入困境的地方。该函数按预期工作,但在类型提示方面,我无法找出一种方式来表达 “返回类型与 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")
有人可以帮助我或给我如何继续的提示吗?
谢谢!
这里的语法有点可怕,但我认为没有更干净的方法来做到这一点。以下通过 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, you have to use
typing.Type
而不是内置的type
。)
T
在顶部定义为“bound”到 CustomEnum
类的类型变量。这意味着用 T
注解的变量只能是 CustomEnum
的实例或继承自 CustomEnum
的类的实例。
在上面的类方法中,我们实际上使用此类型变量来定义
cls
参数相对于返回类型的类型。通常我们会做相反的事情——我们通常根据函数的输入参数的类型来定义函数的返回类型。所以如果这感觉有点令人费解,这是可以理解的!
我们是说:这个方法会导致一个类的实例 - 我们不知道该类是什么,但我们知道它将是
CustomEnum
或继承自 CustomEnum
的类。我们还知道,无论返回什么类,我们都可以保证函数中cls
参数的类型将在类型层次结构中从返回值的类型“上一级”。
在很多情况下,我们可能知道
type[cls]
始终是一个固定值。在这些情况下,可以将其硬编码到类型注释中。但是,最好不这样做,而是使用此方法,它清楚地显示输入类型和返回类型之间的关系(即使它使用可怕的语法来这样做!)。
进一步阅读:有关 类对象类型的 MyPy 文档。
对于绝大多数类(不包括
Enum
,它们使用元类,但我们暂时将其放在一边),以下内容将成立:
示例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
对于
cls
方法的 CustomEnum.random()
参数,我们在上面的示例 1中注释了
A
的等效项,而不是 instance_of_a
。
instance_of_a
的类型是A
。A
的类型不是 A
— A
是一个类,而不是类的实例。type
的实例,要么是继承自 type
的自定义元类的实例。A
的类型是type
。规则如下:
type
或(如果你太聪明了)继承自 type
的自定义元类。通过您的
CustomEnum
类,我们可以使用 cls
模块使用的元类来注释 enum
参数(enum.EnumType
,如果您想知道)。但是,正如我所说,最好不要这样做。我建议的解决方案更清楚地说明了输入类型和返回类型之间的关系。
从 Python 3.11 开始,此代码的正确返回注释是
Self
:
from typing import Self
class CustomEnum(Enum):
@classmethod
def random(cls) -> Self:
return random.choice(list(cls))
引用PEP:
此 PEP 引入了一种简单直观的方法来注释返回类实例的方法。这与 PEP 484 中指定的基于 TypeVar 的方法的行为相同,但更简洁且更易于遵循。
当前的解决方法不直观且容易出错:
Self = TypeVar("Self", bound="Shape") class Shape: @classmethod def from_config(cls: type[Self], config: dict[str, float]) -> Self: return cls(config["scale"])
我们建议直接使用
:Self
from typing import Self class Shape: @classmethod def from_config(cls, config: dict[str, float]) -> Self: return cls(config["scale"])
这避免了复杂的
注释和带有绑定的 TypeVar 声明。同样,后一个代码的行为与前一个代码相同。cls: type[Self]