Python 还能这样?Python 中 IntEnum 与 StrEnum 新枚举类型

一、IntEnum:数值枚举首选

咱们先来说说 IntEnum,它可是数值枚举的首选。为啥这么说呢?因为它继承自整数,这就意味着它的成员能直接当整数用,特别方便。

比如说 HTTP 状态码,咱们定义一个 Status 的 IntEnum,像 Status.OK 就对应 200,用起来那叫一个顺手。咱们来看段代码感受下:

from enum import IntEnumclass Status(IntEnum):OK = 200BAD_REQUEST = 400NOT_FOUND = 404INTERNAL_ERROR = 500# 直接作为整数使用print(Status.OK)  # 输出:Status.OKprint(Status.OK.value)  # 输出:200print(int(Status.OK))  # 输出:200# 数值比较if Status.OK > 0:print("状态码正常")  # 会输出这句话# 数值运算new_status = Status.OK + 100print(new_status)  # 输出:300

从上面的代码能看出,IntEnum 的成员既可以像枚举一样表示具体的状态,又能直接当作整数进行比较和运算。这在处理错误码、状态标记这些需要数字标识的场景时,就不用咱们手动去维护数值映射了,省了不少事。

二、StrEnum:字符串枚举利器

再来说说 StrEnum,它是字符串枚举的利器。它的成员会自动被视为字符串,就像角色权限里的 Role.ADMIN,直接对应着 "admin"。而且它还支持字符串的各种方法,也能进行精确匹配。

更方便的是,配合 auto 功能,它能自动以成员名作为值,比如 Role.ADMIN 默认值就是 "admin",这样代码的可读性一下子就上去了。咱们看段代码:

from enum import StrEnum, autoclass Role(StrEnum):ADMIN = "admin"USER = "user"GUEST = auto()  # 使用auto,值会是成员名的小写,也就是"guest"# 直接当作字符串使用print(Role.ADMIN)  # 输出:Role.ADMINprint(Role.ADMIN.value)  # 输出:adminprint(str(Role.ADMIN))  # 输出:admin# 字符串方法print(Role.ADMIN.upper())  # 输出:ADMIN# 精确匹配if Role.ADMIN == "admin":print("是管理员")  # 会输出这句话print(Role.GUEST.value)  # 输出:guest

这段代码里,GUEST 使用了 auto,它的值就是 "guest",是不是很方便?

三、IntEnum、StrEnum 与普通枚举的对比

为了更清楚地看出它们之间的区别,咱们用表格来展示一下:

特性 IntEnum StrEnum 普通枚举
是否需要显式声明值 否(可自动生成,也可手动指定) 否(可自动生成,也可手动指定) 是(不指定的话有默认规则,但不好控制)
类型明确性 明确(整数类型) 明确(字符串类型) 相对不明确(默认是 Enum 类型)
能否融入对应类型操作逻辑 能(参与数学运算等) 能(使用字符串方法等) 不能(需要额外转换)
是否减少类型转换代码
是否减少错误检查代码

咱们再通过代码来感受下普通枚举和它们的区别。先看普通枚举:

from enum import Enumclass CommonStatus(Enum):OK = 200BAD_REQUEST = 400# 普通枚举不能直接当作整数运算try:print(CommonStatus.OK + 100)except TypeError as e:print(e)  # 输出: unsupported operand type(s) for +: 'CommonStatus' and 'int'class CommonRole(Enum):ADMIN = "admin"USER = "user"# 普通枚举不能直接使用字符串方法try:print(CommonRole.ADMIN.upper())except AttributeError as e:print(e)  # 输出: 'CommonRole' object has no attribute 'upper'

再对比下 IntEnum 和 StrEnum 的代码,就能明显看出它们的优势了。

四、多态讲解

多态说白了就是不同的对象,在调用同一个方法的时候,会有不同的表现。就好比 IntEnum 和 StrEnum,它们都属于枚举类型,但在进行一些操作时,表现可不一样。

咱们看个例子,定义一个函数,接收一个枚举成员,然后进行相应的操作:

from enum import IntEnum, StrEnumclass NumberEnum(IntEnum):ONE = 1TWO = 2class StringEnum(StrEnum):ONE = "one"TWO = "two"def do_something(enum_member):# 如果是IntEnum的成员,就进行加法运算if isinstance(enum_member, IntEnum):print(enum_member + 10)# 如果是StrEnum的成员,就进行大写转换elif isinstance(enum_member, StrEnum):print(enum_member.upper())do_something(NumberEnum.ONE)  # 输出:11do_something(StringEnum.ONE)  # 输出:ONE

在这个例子里,do_something 函数接收不同的枚举类型成员,会根据它们的类型做出不同的处理,这就是多态的体现。

再举个更贴近实际应用的例子,比如处理不同类型的配置项:

from enum import IntEnum, StrEnumclass IntConfig(IntEnum):MAX_SIZE = 1024MIN_SIZE = 64class StrConfig(StrEnum):FILE_TYPE = "txt"ENCODING = "utf-8"def process_config(config):# 处理整数配置,进行倍数运算if isinstance(config, IntEnum):print(f"处理整数配置:{config} 的两倍是 {config * 2}")# 处理字符串配置,进行拼接elif isinstance(config, StrEnum):print(f"处理字符串配置:{config} 拼接后是 {config + '_config'}")process_config(IntConfig.MAX_SIZE)  # 输出:处理整数配置:IntConfig.MAX_SIZE 的两倍是 2048process_config(StrConfig.FILE_TYPE)  # 输出:处理字符串配置:StrConfig.FILE_TYPE 拼接后是 txt_config

这里的 process_config 函数就像一个通用的处理器,不管传入的是 IntEnum 还是 StrEnum 的成员,都能根据它们的类型进行相应的处理,充分体现了多态的灵活性。

五、使用 Python 多态时的常见问题和错误

  1. 类型判断错误:在多态场景中,很容易用错类型判断的方法,比如用 type () 而不是 isinstance ()。type () 只能判断精确的类型,而 isinstance () 会考虑继承关系。
from enum import IntEnumclass MyIntEnum(IntEnum):A = 1# 错误示例,用type()判断enum_member = MyIntEnum.Aif type(enum_member) is IntEnum:print("是IntEnum类型")  # 不会输出,因为type(enum_member)是MyIntEnumelse:print("不是IntEnum类型")  # 会输出# 正确示例,用isinstance()判断if isinstance(enum_member, IntEnum):print("是IntEnum类型")  # 会输出else:print("不是IntEnum类型")
  1. 方法重写不当:在子类中重写父类方法时,如果参数或返回值类型不符合预期,可能会导致多态调用出现错误。
from enum import IntEnumclass ParentEnum(IntEnum):def do_action(self, x):return self + xclass ChildEnum(ParentEnum):A = 1B = 2# 错误重写,参数类型不对def do_action(self, x: str):return self.value + xtry:print(ChildEnum.A.do_action(10))except TypeError as e:print(e)  # 输出:can only concatenate str (not "int") to str
  1. 忽略多态的前提:多态的实现需要基于继承关系,如果没有正确的继承结构,就无法实现多态。比如一个类和另一个类没有继承关系,却想让它们实现多态,就会出问题。

六、面试相关问题及回答

  1. 问题:什么是 Python 的多态?在 IntEnum 和 StrEnum 中如何体现? 回答:Python 的多态就是不同的对象调用同一个方法,会有不同的表现。在 IntEnum 和 StrEnum 中,它们都是枚举类型,都可以被当作枚举来使用,但当进行运算或调用方法时,IntEnum 能进行数学运算,StrEnum 能使用字符串方法,这就是多态的体现。

  2. 问题:IntEnum 和普通枚举有什么区别? 回答:IntEnum 继承自整数,它的成员可以直接当作整数进行运算和比较,不需要额外的类型转换;而普通枚举的成员是 Enum 类型,不能直接参与整数相关的操作,需要先转换类型。而且 IntEnum 的类型更明确,能减少错误。

  3. 问题:使用 StrEnum 时,auto () 功能有什么用? 回答:auto () 功能在 StrEnum 中可以让成员的值自动设为成员名的小写形式,这样就不用我们手动去声明字符串值了,既节省了代码,又提高了可读性,还能避免手动输入时可能出现的错误。

  4. 问题:在使用多态时,要注意什么? 回答:首先要确保类之间有正确的继承关系,这是多态的基础;其次在进行类型判断时,尽量使用 isinstance () 而不是 type (),因为 isinstance () 会考虑继承;另外,重写方法时要注意参数和返回值的类型,避免出现调用错误。

  5. 问题:为什么说 IntEnum 和 StrEnum 能减少类型转换和错误检查代码? 回答:因为 IntEnum 的成员本身就可以当作整数用,能直接参与数学运算,不用再把它转换成整数;StrEnum 的成员能直接使用字符串方法,不用转换成字符串。这样就省去了很多类型转换的代码,同时因为它们类型明确,也减少了因类型错误而需要做的错误检查。