子类中返回值的类型提示 [英] type hint for return value in subclass
问题描述
我正在编写一个 CustomEnum 类,我想在其中添加一些帮助器方法,然后子类化我的 CustomEnum 的类可以使用这些方法.其中一种方法是返回一个随机枚举值,这就是我被卡住的地方.该函数按预期工作,但在类型提示方面,我想不出一种说法返回类型与 cls 类型相同".
I am writing a CustomEnum class in which I want to add some helper methods, that would then be available by the classes subclassing my CustomEnum. One of the methods is to return a random enum value, and this is where I am stuck. The function works as expected, but on the type-hinting side, I cannot figure out a way of saying "the return type is the same type of cls".
我相当确定其中涉及到一些 TypeVar
或类似的魔法,但由于我从未使用过它们,因此我从未花时间弄清楚它们.
I am fairly sure there's some TypeVar
or similar magic involved, but since I never had to use them I never took the time to figure them out.
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")
有人可以帮助我或给我一个关于如何进行的提示吗?
Can somebody help me or give me a hint on how to proceed?
谢谢!
推荐答案
这里的语法有点可怕,但我认为没有更简洁的方法来做到这一点.
The syntax here is kind of horrible, but I don't think there's a cleaner way to do this.
from typing import TypeVar
from enum import Enum
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
.)
(In python versions <= 3.8, you have to use typing.Type
rather than the builtin type
if you want to parameterise it.)
这是怎么回事?
T
在顶部定义为绑定"类型变量.到 CustomEnum
类.这意味着用 T
注释的变量只能是 CustomEnum
的实例或继承自 CustomEnum
的类的实例.
T
is defined at the top as being a type variable that is "bound" to the CustomEnum
class. This means that a variable annotated with T
can only be an instance of CustomEnum
or an instance of a class inheriting from CustomEnum
.
在上面的类方法中,我们实际上是使用这个类型变量来定义与返回类型相关的 cls
参数的类型.通常我们做相反的事情——我们通常根据函数的输入参数的类型定义函数的返回类型.因此,如果这感觉有点令人费解,这是可以理解的!
In the classmethod above, we're actually using this type-variable to define the type of the cls
parameter with respect to the return type. Usually we do the opposite — we usually define a function's return types with respect to the types of that function's input parameters. So it's understandable if this feels a little mind-bending!
我们说:这个方法会导致一个类的实例——我们不知道这个类是什么,但我们知道它要么是CustomEnum
,要么是一个继承自自定义枚举
.我们也知道无论返回什么类,我们都可以保证函数中cls
参数的类型是上一级"的.在来自返回值类型的类型层次结构中.
We're saying: this method leads to instances of a class — we don't know what the class will be, but we know it will either be CustomEnum
or a class inheriting from CustomEnum
. We also know that whatever class is returned, we can guarantee that the type of the cls
parameter in the function will be "one level up" in the type heirarchy from the type of the return value.
在很多情况下,我们可能知道 type[cls]
将始终是一个固定值.在这些情况下,可以将其硬编码到类型注释中.但是,最好不要这样做,而是使用这种方法,它清楚地显示了输入类型和返回类型之间的关系(即使它使用了可怕的语法来这样做!).
In a lot of situations, we might know that type[cls]
will always be a fixed value. In those situations, it would be possible to hardcode that into the type annotations. However, it's best not to do so, and instead to use this method, which clearly shows the relationship between the type of the input and the return type (even if it uses horrible syntax to do so!).
进一步的解释和例子
对于绝大多数类(不使用 Enum
s,它们使用元类,但让我们暂时将其搁置一旁),以下将适用:
For the vast majority of classes (not with Enum
s, they use metaclasses, but let's leave that aside for the moment), the following will hold true:
示例 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
参数,我们注释了 A
而不是 instance_of_a
在我上面的示例 1 中.
For the cls
parameter of your CustomEnum.random()
method, we're annotating the equivalent of A
rather than instance_of_a
in my example 1 above.
instance_of_a
的类型是A
.- 但是
A
的类型不是A
——A
是一个类,而不是一个类的实例. - 类不是类的实例;它们要么是
type
的实例,要么是从type
继承的自定义元类的实例. - 这里没有使用元类;所以,
A
的类型是type
.
- The type of
instance_of_a
isA
. - But the type of
A
is notA
—A
is a class, not an instance of a class. - Classes are not instances of classes; they are either instances of
type
or instances of custom metaclasses that inherit fromtype
. - No metaclasses are being used here; ergo, the type of
A
istype
.
规则如下:
- 所有 python 类实例的类型将是它们所属的类.
- 所有 python 类 的类型要么是
type
,要么是(如果你太聪明了)从继承的自定义元类输入
.
- The type of all python class instances will be the class they're an instance of.
- The type of all python classes will be either
type
or (if you're being too clever for your own good) a custom metaclass that inherits fromtype
.
使用您的 CustomEnum
类,我们可以用 enum
模块使用的元类注释 cls
参数(enum.EnumType
, 如果你想知道).但是,正如我所说 - 最好不要.我建议的解决方案更清楚地说明了输入类型和返回类型之间的关系.
With your CustomEnum
class, we could annotate the cls
parameter with the metaclass that the enum
module uses (enum.EnumType
, if you wanna know). But, as I say — best not to. The solution I've suggested illustrates the relationship between the input type and the return type more clearly.
这篇关于子类中返回值的类型提示的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!