使用 Python 的 Enum 构建常量枚举

2024 年 8 月 29 日 | 阅读 11 分钟

在本教程中,我们将学习如何使用 Python 的 enum 方法构建常量枚举。我们将讨论如何在 Python 中使用枚举及其成员,并使用新功能自定义枚举类。

Python 中枚举的介绍

有许多其他编程语言,例如 Java、C++,它们包含支持枚举或 enum 的语法。这种数据类型允许创建可以通过枚举本身访问的语义相关的常量。然而,Python 没有专门用于 enum 的语法。但是 Python 标准库提供了 enum 模块,它有助于使用 Enum 类进行枚举。

枚举的几个示例包括一周中的几天、一年中的月份和季节、地球的基点方向、程序的状​​态码、HTTP 状态码、交通灯中的颜色以及网络服务的定价计划。换句话说,一组有限的可能值可以称为枚举。

根据 PEP 435 -

枚举是一组绑定到唯一常量值的符号名称。在枚举中,可以通过标识符比较值,枚举本身可以迭代。

我们也可以在不使用 Enum 类的情况下,通过定义一系列相似或相关的常量来创建与枚举类似的东西。操作如下:

使用这种习惯用法来分组许多相关常量的一个问题是它需要更好地扩展。此外,第一个常量的值将为 0,这在 Python 中是假值。这在某些情况下可能会有问题,尤其是涉及布尔测试的情况。为了克服这些限制,可以使用替代方法。一种这样的方法是定义一个具有类级别常量的类。使用类,我们可以以可扩展的方式对相关常量进行分组。此外,我们可以为每个常量使用唯一的名称,这避免了将假值作为第一个常量的问题。这是一个例子

示例 -

在许多情况下,使用枚举可以更好地替代上述习惯用法,因为它可以帮助您避免缺点并生成更组织化、更具可读性和更健壮的代码。枚举有几个好处,其中一些与编码的便利性有关。例如

  • 枚举允许我们定义一组命名的相关常量,这使您的代码更有条理和可读性。这是因为很明显这些常量是相关的,我们不必搜索它们所有被使用的地方。
  • 它使我们的代码更健壮,因为我们可以定义一组固定的允许值。这意味着如果有人尝试使用无效值,您可以及早捕获它,而无需稍后调试问题。
  • 枚举可以帮助防止由打字错误或其他错误引起的难以发现的错误。由于枚举定义了一组固定的值,我们可以使用它们而无需担心拼写错误或使用错误的值。
  • 枚举通常比魔术数字或其他常量更容易阅读和理解,而后者可能不清楚或模棱两可。

使用枚举可以使您的代码更具组织性、可读性和健壮性,同时使其更易于编写和调试。

一旦您对编程和 Python 中的枚举有了基本的了解,您就可以使用 Python 的 Enum 类来定义自己的枚举类型。Enum 类是 Python 的一个内置类,允许您定义具有命名常量的枚举。

使用 Python 的 Enum 创建枚举

Python 的 enum 模块包含 Enum 类,它提供了一种简单的方法来创建枚举类型。我们可以使用子类化方法或 Enum 类提供的函数式 API 来创建自己的枚举。

子类化 Enum 类允许我们将自己的一组相关常量定义为类定义中的枚举成员。或者,我们可以使用函数式 API 通过将枚举的名称及其成员作为参数传递给 Enum() 函数来创建枚举。

Python enum 模块提供了一个通用的枚举类型,它具有迭代和比较功能。使用此类型,您可以定义一组命名常量,这些常量可以替换常见数据类型的字面量,例如数字和字符串。

通过使用此类型,您可以通过为常量赋予有意义的名称而不是在整个代码中使用字面量来提高代码的可读性和可维护性。这使您的代码更易于理解,并降低因打字错误或不正确的值而导致错误的风险。

示例 -

输出

[, , , , , , , , , , , ]

解释 -

在上面的示例中,我们定义了一个名为 Month 的 Enum 类,它有十二个成员,每个成员代表一年中的一个月。每个成员都被分配了一个对应的整数值,从 1 到 12。

要使用此枚举,您可以简单地按名称引用其成员。例如,Month.JANUARY 代表一年中的第一个月。

总的来说,为月份名称创建枚举可以通过在整个代码中使用有意义的名称而不是字面量来帮助提高代码的可读性和可维护性。

如果我们尝试修改枚举成员的值,将引发 AttributeError。值得注意的是,虽然枚举中的成员名称是常量且不可修改,但枚举本身的名称是变量,可以在程序执行期间的任何时候重新绑定。但是,通常不建议重新绑定枚举的名称,因为这样做可能会导致混淆并使代码难以理解。通常最好在整个程序中始终使用枚举的原始名称来引用其成员。

枚举可以看作是常量的集合,例如列表、元组或字典,并且也是可迭代的。这就是为什么我们可以使用 list() 将枚举转换为枚举成员列表。

示例 -

输出



我们还可以使用基于 range() 的习语来构建枚举。

示例 -

输出

[, , , ]

Enum 类的语法类似于常规 Python 类,但它们是与普通 Python 类不同的特殊类。主要区别在于:

  • 枚举成员是常量,在运行时不能修改。
  • 枚举成员是唯一的,不能在同一枚举中重复。
  • 枚举成员是可哈希的,可以用作字典中的键或集合中的元素。
  • 枚举类不能直接子类化或实例化。

这些差异使枚举成为在 Python 中创建一组命名常量的强大工具,这些常量组织良好、可读且健壮。通过使用枚举,您可以帮助防止错误并使您的代码更易于理解和维护。

虽然枚举成员通常具有连续的整数值,但 Python 允许枚举成员的值可以是任何类型,包括用户定义类型。例如,考虑以下学校等级枚举,其中非连续的数字值按降序使用

示例 -

解释 -

在此示例中,我们定义了一个名为 Grade 的 Enum 类,它有六个成员,每个成员代表一个不同的学校等级。我们不使用连续的整数值,而是为每个成员使用一个元组来存储数值和表示等级描述的字符串。

要访问成员的值或描述,您可以引用枚举成员的相应属性。例如,Grade.A_PLUS.value 将返回元组 (95, "Excellent"),而 Grade.A_PLUS.value[0] 将返回数值 95。

总的来说,为枚举成员使用非连续值和用户定义类型的能力使 Python 枚举比其他一些编程语言中的 Enum 更灵活和强大。

在 Python 中,我们可以为枚举成员使用字符串值。这是一个 Size 枚举的示例,可用于在线商店

示例 -

在上面的示例中,我们定义了一个名为 Size 的 Enum 类,它有四个成员,每个成员代表在线商店产品的不同尺寸。我们不使用整数值或其他类型,而是为每个成员使用字符串值。

要在代码中使用这些枚举成员,我们可以直接使用它们的名称引用它们,如下所示

我们可以创建仅包含两个值的布尔值枚举类。

示例 -

上面的示例展示了如何为我们的代码添加额外的上下文。该代码提供了更好的可读性,模拟了一个具有两种可能状态的 switch 对象。

我们还可以定义异构值。

示例 -

输出

UserResponse.NO
UserResponse.YES

为常量使用不同的数据类型可能会导致不一致并损害类型安全,因此通常不建议这样做。最好将相同数据类型的相关常量分组到枚举中,以确保一致性并提高类型安全。

使用函数式 API 创建枚举

使用 Enum 类,我们可以使用函数式 API 而不是传统的类语法创建枚举。只需使用适当的参数调用 Enum 类,类似于调用函数或任何其他可调用对象。

用于创建 Enum 类的函数式 API 在结构上类似于 namedtuple() 工厂函数的工作方式。对于 Enum,函数签名遵循以下格式

下表显示了 Enum 签名中每个参数的含义。

参数描述必需
它接受包含新枚举类名称的字符串。是的
名称它提供枚举成员的名称。是的
模块它接受定义枚举类的模块名称不能
限定名它接受定义枚举类的模块的位置。不能
type它存储一个类,用作第一个混合类。不能
开始 (start)它接受枚举值将开始的起始值。不能

如果我们需要 pickle 和 unpickle 枚举,模块和限定名是必不可少的。

创建 Enum 类时,如果未明确设置 module 参数,Python 将尝试自动确定模块。但是,如果失败,则生成的类可能无法 pickle。类似地,如果未设置 qualname 参数,Python 将其设置为全局范围,这在某些情况下 unpickle 枚举时可能会导致问题。因此,通常建议在创建 Enum 类时明确设置 module 和 qualname。

为了将额外的功能(例如扩展的比较功能)整合到枚举中,我们可以在创建 Enum 时使用 type 参数将混合类作为参数传递。包含混合类允许我们提供具有超出默认 Enum 类所提供功能的自定义枚举。

start 参数允许自定义枚举成员的初始值。默认情况下,此参数设置为 1 而不是 0。之所以使用此默认值,是因为 0 的布尔值为 False,而枚举成员的评估结果为 True。因此,从 0 开始可能会出乎意料并导致混淆,因此默认值为 1。

示例 -

从自动值构建枚举

Python 的 enum 模块提供了一个方便的 auto() 函数,可以为枚举成员自动赋值。默认情况下,此函数按定义顺序为每个成员分配连续的整数值。让我们理解以下示例。

示例 -

输出

[, , , , , , 

要使用 auto() 函数进行自动值分配,我们必须为每个需要自动值的枚举成员调用它一次。此外,我们可以将 auto() 与具体值结合使用,类似于示例中定义 Day.WEDNESDAY 和 Day.SUNDAY 的方式。

创建具有别名和唯一值的枚举

我们可以创建两个或多个成员共享相同常量值的枚举。这些重复的成员称为别名,它们在某些情况下很有用。例如,考虑一个表示不同操作系统的枚举 (OS),如以下代码示例所示

示例 -

输出

[, , , , ]

解释 -

在上面的示例中,OS 枚举包含 WINDOWS 成员的别名,名称为 WINDOWS_ALIAS。WINDOWS 和 WINDOWS_ALIAS 都共享值“win”。这在某些情况下很有用,例如当在代码库的不同部分使用多个名称来引用相同的 OS 时。

我们还可以使用 enum 模块中的 @unique 装饰器省略别名中的重复值。

示例 -

输出

File "/usr/lib/python3.11/enum.py", line 1564, in unique
raise ValueError('duplicate values found in %r: %s' %
ValueError: duplicate values found in : WINDOWS_ALIAS -> WINDOWS

比较枚举

枚举可以在条件语句中进行比较,例如 if...elif 和 match...case。这表明枚举成员可以相互比较。

默认情况下,枚举支持两种类型的比较运算符

使用 is 和 is not 运算符进行身份比较,它们检查两个对象是否是同一个实例。

使用 == 和 != 运算符进行值比较,它们检查两个对象是否具有相同的值。

例如,以下代码显示了使用值比较运算符对两个枚举成员进行的基本比较

示例 -

结论

本教程详细概述了 Python 中的 Enum。枚举是编程语言中广泛使用和流行的数据类型,它使开发人员能够组织相关常量并通过枚举访问它们。这通过允许程序员将相关常量分组到一个地方并在整个程序中使用它们来简化编码过程。尽管 Python 没有专门用于枚举的语法,但开发人员仍然可以通过 enum 模块在 Python 中使用这种流行的数据类型,该模块提供了 Enum 类。此类允许程序员通过将相关常量分组到单个命名空间下创建和使用枚举。