在子类中输入返回值提示

问题描述 投票:0回答:2

我正在编写一个 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")

有人可以帮助我或给我如何继续的提示吗?

谢谢!

python mypy python-typing
2个回答
19
投票

这里的语法有点可怕,但我认为没有更干净的方法来做到这一点。以下通过 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

规则如下:

  • 所有 python 类实例的类型将是它们作为实例的类。
  • 所有 python classes 的类型将是
    type
    或(如果你太聪明了)继承自
    type
    的自定义元类。

通过您的

CustomEnum
类,我们可以使用
cls
模块使用的元类来注释
enum
参数(
enum.EnumType
如果您想知道)。但是,正如我所说,最好不要这样做。我建议的解决方案更清楚地说明了输入类型和返回类型之间的关系。


10
投票

从 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"])

这避免了复杂的

cls: type[Self]
注释和带有绑定的 TypeVar 声明。同样,后一个代码的行为与前一个代码相同。

© www.soinside.com 2019 - 2024. All rights reserved.