Enum HOWTO

Enum 是一组绑定到唯一值的符号名称。它们类似于全局变量,但它们提供了更有用的 repr(),分组,类型安全和一些其他功能。

当您有一个变量可以采用有限的选择值之一时,它们最有用。 例如,一周中的几天

>>> from enum import Enum
>>> class Weekday(Enum):
...     MONDAY = 1
...     TUESDAY = 2
...     WEDNESDAY = 3
...     THURSDAY = 4
...     FRIDAY = 5
...     SATURDAY = 6
...     SUNDAY = 7

或者可能是 RGB 原色

>>> from enum import Enum
>>> class Color(Enum):
...     RED = 1
...     GREEN = 2
...     BLUE = 3

如您所见,创建一个 Enum 与编写一个继承自 Enum 本身的类一样简单。

注意

枚举成员的大小写

由于枚举用于表示常量,并且为了避免混入类方法/属性和枚举名称之间的名称冲突问题,我们强烈建议对成员使用 UPPER_CASE 名称,并且将在我们的示例中使用该样式。

根据枚举的性质,成员的值可能重要也可能不重要,但无论如何,该值都可以用于获取相应的成员

>>> Weekday(3)
<Weekday.WEDNESDAY: 3>

如您所见,成员的 repr() 显示枚举名称、成员名称和值。 成员的 str() 仅显示枚举名称和成员名称

>>> print(Weekday.THURSDAY)
Weekday.THURSDAY

枚举成员的类型是它所属的枚举

>>> type(Weekday.MONDAY)
<enum 'Weekday'>
>>> isinstance(Weekday.FRIDAY, Weekday)
True

枚举成员具有一个属性,该属性仅包含其 name

>>> print(Weekday.TUESDAY.name)
TUESDAY

同样,它们也有一个属性用于它们的 value

>>> Weekday.WEDNESDAY.value
3

与许多仅将枚举视为名称/值对的语言不同,Python 枚举可以添加行为。 例如,datetime.date 有两个返回工作日的方法:weekday()isoweekday()。 区别在于其中一个从 0-6 计数,另一个从 1-7 计数。 我们无需自己跟踪它,而是可以向 Weekday 枚举添加一个方法,以从 date 实例中提取日期并返回匹配的枚举成员

@classmethod
def from_date(cls, date):
    return cls(date.isoweekday())

完整的 Weekday 枚举现在看起来像这样

>>> class Weekday(Enum):
...     MONDAY = 1
...     TUESDAY = 2
...     WEDNESDAY = 3
...     THURSDAY = 4
...     FRIDAY = 5
...     SATURDAY = 6
...     SUNDAY = 7
...     #
...     @classmethod
...     def from_date(cls, date):
...         return cls(date.isoweekday())

现在我们可以找出今天是什么日子了! 观察

>>> from datetime import date
>>> Weekday.from_date(date.today())     
<Weekday.TUESDAY: 2>

当然,如果您在其他日子阅读此内容,您会看到那一天。

如果我们的变量只需要一天,则此 Weekday 枚举非常有用,但是如果我们需要几天呢? 也许我们正在编写一个函数来绘制一周中的家务,并且不想使用 list – 我们可以使用另一种类型的 Enum

>>> from enum import Flag
>>> class Weekday(Flag):
...     MONDAY = 1
...     TUESDAY = 2
...     WEDNESDAY = 4
...     THURSDAY = 8
...     FRIDAY = 16
...     SATURDAY = 32
...     SUNDAY = 64

我们更改了两件事:我们继承自 Flag,并且值都是 2 的幂。

就像上面的原始 Weekday 枚举一样,我们可以进行单项选择

>>> first_week_day = Weekday.MONDAY
>>> first_week_day
<Weekday.MONDAY: 1>

但是 Flag 还允许我们将多个成员组合到一个变量中

>>> weekend = Weekday.SATURDAY | Weekday.SUNDAY
>>> weekend
<Weekday.SATURDAY|SUNDAY: 96>

你甚至可以迭代一个 Flag 变量

>>> for day in weekend:
...     print(day)
Weekday.SATURDAY
Weekday.SUNDAY

好吧,让我们设置一些家务活

>>> chores_for_ethan = {
...     'feed the cat': Weekday.MONDAY | Weekday.WEDNESDAY | Weekday.FRIDAY,
...     'do the dishes': Weekday.TUESDAY | Weekday.THURSDAY,
...     'answer SO questions': Weekday.SATURDAY,
...     }

以及一个显示给定日期家务的函数

>>> def show_chores(chores, day):
...     for chore, days in chores.items():
...         if day in days:
...             print(chore)
...
>>> show_chores(chores_for_ethan, Weekday.SATURDAY)
answer SO questions

在成员的实际值无关紧要的情况下,您可以节省一些工作并为值使用 auto()

>>> from enum import auto
>>> class Weekday(Flag):
...     MONDAY = auto()
...     TUESDAY = auto()
...     WEDNESDAY = auto()
...     THURSDAY = auto()
...     FRIDAY = auto()
...     SATURDAY = auto()
...     SUNDAY = auto()
...     WEEKEND = SATURDAY | SUNDAY

以编程方式访问枚举成员及其属性

有时以编程方式访问枚举中的成员很有用(即 Color.RED 不起作用的情况,因为在编写程序时确切的颜色是未知的)。 Enum 允许这种访问

>>> Color(1)
<Color.RED: 1>
>>> Color(3)
<Color.BLUE: 3>

如果要按名称访问枚举成员,请使用项目访问

>>> Color['RED']
<Color.RED: 1>
>>> Color['GREEN']
<Color.GREEN: 2>

如果你有一个枚举成员并且需要它的 namevalue

>>> member = Color.RED
>>> member.name
'RED'
>>> member.value
1

复制枚举成员和值

具有两个名称相同的枚举成员是无效的

>>> class Shape(Enum):
...     SQUARE = 2
...     SQUARE = 3
...
Traceback (most recent call last):
...
TypeError: 'SQUARE' already defined as 2

但是,一个枚举成员可以有其他与之关联的名称。给定两个具有相同值的条目 AB(并且首先定义了 A),B 是成员 A 的别名。按值查找 A 的值将返回成员 A。按名称查找 A 将返回成员 A。 按名称查找 B 也将返回成员 A

>>> class Shape(Enum):
...     SQUARE = 2
...     DIAMOND = 1
...     CIRCLE = 3
...     ALIAS_FOR_SQUARE = 2
...
>>> Shape.SQUARE
<Shape.SQUARE: 2>
>>> Shape.ALIAS_FOR_SQUARE
<Shape.SQUARE: 2>
>>> Shape(2)
<Shape.SQUARE: 2>

注意

尝试创建与已定义的属性(另一个成员、方法等)同名的成员,或尝试创建与成员同名的属性是不允许的。

确保枚举值唯一

默认情况下,枚举允许使用多个名称作为同一值的别名。 如果不需要此行为,可以使用 unique() 装饰器

>>> from enum import Enum, unique
>>> @unique
... class Mistake(Enum):
...     ONE = 1
...     TWO = 2
...     THREE = 3
...     FOUR = 3
...
Traceback (most recent call last):
...
ValueError: duplicate values found in <enum 'Mistake'>: FOUR -> THREE

使用自动值

如果确切的值不重要,您可以使用 auto

>>> from enum import Enum, auto
>>> class Color(Enum):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> [member.value for member in Color]
[1, 2, 3]

这些值由 _generate_next_value_() 选择,该方法可以被覆盖

>>> class AutoName(Enum):
...     @staticmethod
...     def _generate_next_value_(name, start, count, last_values):
...         return name
...
>>> class Ordinal(AutoName):
...     NORTH = auto()
...     SOUTH = auto()
...     EAST = auto()
...     WEST = auto()
...
>>> [member.value for member in Ordinal]
['NORTH', 'SOUTH', 'EAST', 'WEST']

注意

_generate_next_value_() 方法必须在任何成员之前定义。

迭代

迭代枚举的成员不会提供别名

>>> list(Shape)
[<Shape.SQUARE: 2>, <Shape.DIAMOND: 1>, <Shape.CIRCLE: 3>]
>>> list(Weekday)
[<Weekday.MONDAY: 1>, <Weekday.TUESDAY: 2>, <Weekday.WEDNESDAY: 4>, <Weekday.THURSDAY: 8>, <Weekday.FRIDAY: 16>, <Weekday.SATURDAY: 32>, <Weekday.SUNDAY: 64>]

请注意,别名 Shape.ALIAS_FOR_SQUAREWeekday.WEEKEND 未显示。

特殊属性 __members__ 是名称到成员的只读有序映射。 它包括枚举中定义的所有名称,包括别名

>>> for name, member in Shape.__members__.items():
...     name, member
...
('SQUARE', <Shape.SQUARE: 2>)
('DIAMOND', <Shape.DIAMOND: 1>)
('CIRCLE', <Shape.CIRCLE: 3>)
('ALIAS_FOR_SQUARE', <Shape.SQUARE: 2>)

__members__ 属性可用于对枚举成员进行详细的编程访问。 例如,查找所有别名

>>> [name for name, member in Shape.__members__.items() if member.name != name]
['ALIAS_FOR_SQUARE']

注意

标志的别名包括设置了多个标志的值,例如 3,以及没有设置任何标志的值,即 0

比较

枚举成员按标识进行比较

>>> Color.RED is Color.RED
True
>>> Color.RED is Color.BLUE
False
>>> Color.RED is not Color.BLUE
True

支持枚举值之间的有序比较。 枚举成员不是整数(但请参见下面的 IntEnum

>>> Color.RED < Color.BLUE
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'Color' and 'Color'

但是定义了相等比较

>>> Color.BLUE == Color.RED
False
>>> Color.BLUE != Color.RED
True
>>> Color.BLUE == Color.BLUE
True

与非枚举值的比较将始终比较为不相等(再次强调,IntEnum 的设计初衷就是行为不同,请见下文)

>>> Color.BLUE == 2
False

警告

模块可以被重新加载——如果重新加载的模块包含枚举,它们将被重新创建,并且新的成员可能与原始成员不完全相同/相等。

枚举的允许成员和属性

上面的大多数示例都使用整数作为枚举值。使用整数简短方便(并且由函数式 API 提供默认值),但并非强制要求。在绝大多数用例中,人们并不关心枚举的实际值是什么。但是如果值重要的,枚举可以有任意值。

枚举是 Python 类,并且可以像往常一样拥有方法和特殊方法。如果我们有以下枚举

>>> class Mood(Enum):
...     FUNKY = 1
...     HAPPY = 3
...
...     def describe(self):
...         # self is the member here
...         return self.name, self.value
...
...     def __str__(self):
...         return 'my custom str! {0}'.format(self.value)
...
...     @classmethod
...     def favorite_mood(cls):
...         # cls here is the enumeration
...         return cls.HAPPY
...

那么

>>> Mood.favorite_mood()
<Mood.HAPPY: 3>
>>> Mood.HAPPY.describe()
('HAPPY', 3)
>>> str(Mood.FUNKY)
'my custom str! 1'

允许的规则如下:以单个下划线开头和结尾的名称由枚举保留,不能使用;枚举中定义的所有其他属性将成为此枚举的成员,特殊方法(__str__()__add__() 等)、描述符(方法也是描述符)以及 _ignore_ 中列出的变量名除外。

注意:如果你的枚举定义了 __new__() 和/或 __init__(),则传递给枚举成员的任何值都将传递给这些方法。有关示例,请参见 Planet

注意

如果定义了 __new__() 方法,则在创建枚举成员期间使用它;然后它将被 Enum 的 __new__() 替换,该方法在类创建后用于查找现有成员。有关更多详细信息,请参见 何时使用 __new__() 与 __init__()

受限的枚举子类化

新的 Enum 类必须有一个基础枚举类,最多一个具体的数据类型,以及根据需要多个基于 object 的 mixin 类。这些基类的顺序是

class EnumName([mix-in, ...,] [data-type,] base-enum):
    pass

此外,仅当枚举未定义任何成员时,才允许对枚举进行子类化。因此,这是禁止的

>>> class MoreColor(Color):
...     PINK = 17
...
Traceback (most recent call last):
...
TypeError: <enum 'MoreColor'> cannot extend <enum 'Color'>

但是这是允许的

>>> class Foo(Enum):
...     def some_behavior(self):
...         pass
...
>>> class Bar(Foo):
...     HAPPY = 1
...     SAD = 2
...

允许对定义成员的枚举进行子类化会导致违反类型和实例的一些重要不变量。另一方面,允许在一组枚举之间共享一些通用行为是有意义的。(有关示例,请参见 OrderedEnum。)

数据类支持

当从 dataclass 继承时,__repr__() 省略了继承类的名称。例如

>>> from dataclasses import dataclass, field
>>> @dataclass
... class CreatureDataMixin:
...     size: str
...     legs: int
...     tail: bool = field(repr=False, default=True)
...
>>> class Creature(CreatureDataMixin, Enum):
...     BEETLE = 'small', 6
...     DOG = 'medium', 4
...
>>> Creature.DOG
<Creature.DOG: size='medium', legs=4>

使用 dataclass() 参数 repr=False 以使用标准的 repr()

在 3.12 版本中更改:只在值区域显示数据类字段,而不是数据类的名称。

注意

不支持将 dataclass() 装饰器添加到 Enum 及其子类。它不会引发任何错误,但会在运行时产生非常奇怪的结果,例如成员彼此相等

>>> @dataclass               # don't do this: it does not make any sense
... class Color(Enum):
...    RED = 1
...    BLUE = 2
...
>>> Color.RED is Color.BLUE
False
>>> Color.RED == Color.BLUE  # problem is here: they should not be equal
True

序列化

枚举可以被序列化和反序列化

>>> from test.test_enum import Fruit
>>> from pickle import dumps, loads
>>> Fruit.TOMATO is loads(dumps(Fruit.TOMATO))
True

通常的序列化限制适用:可序列化的枚举必须在模块的顶层定义,因为反序列化需要它们可以从该模块导入。

注意

使用 pickle 协议版本 4,可以轻松地序列化嵌套在其他类中的枚举。

可以通过在枚举类中定义 __reduce_ex__() 来修改枚举成员的序列化/反序列化方式。默认方法是按值,但是具有复杂值的枚举可能希望使用按名称

>>> import enum
>>> class MyEnum(enum.Enum):
...     __reduce_ex__ = enum.pickle_by_enum_name

注意

不建议对标志使用按名称,因为未命名的别名将不会被反序列化。

函数式 API

Enum 类是可调用的,提供以下函数式 API

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG')
>>> Animal
<enum 'Animal'>
>>> Animal.ANT
<Animal.ANT: 1>
>>> list(Animal)
[<Animal.ANT: 1>, <Animal.BEE: 2>, <Animal.CAT: 3>, <Animal.DOG: 4>]

此 API 的语义类似于 namedtupleEnum 调用的第一个参数是枚举的名称。

第二个参数是枚举成员名称的。它可以是由空格分隔的名称字符串,名称序列,带有键/值对的 2 元组序列,或名称到值的映射(例如字典)。最后两个选项允许为枚举分配任意值;其他选项会自动分配从 1 开始递增的整数(使用 start 参数来指定不同的起始值)。返回从 Enum 派生的新类。换句话说,上面对 Animal 的赋值等同于

>>> class Animal(Enum):
...     ANT = 1
...     BEE = 2
...     CAT = 3
...     DOG = 4
...

默认使用 1 而不是 0 作为起始数字的原因是 0 在布尔意义上是 False,但是默认情况下,枚举成员都评估为 True

使用函数式 API 创建的枚举的序列化可能很棘手,因为使用帧栈实现细节来尝试确定枚举是在哪个模块中创建的(例如,如果您在单独的模块中使用实用程序函数,它将失败,并且可能在 IronPython 或 Jython 上也不起作用)。解决方案是显式指定模块名称,如下所示

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', module=__name__)

警告

如果未提供 module 并且 Enum 无法确定它是什么,则新的 Enum 成员将无法反序列化;为了使错误更接近源头,将禁用序列化。

新的 pickle 协议 4 也在某些情况下依赖于 __qualname__ 设置为 pickle 将能够找到该类的位置。例如,如果该类在全局范围内的类 SomeData 中可用

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', qualname='SomeData.Animal')

完整的签名是

Enum(
    value='NewEnumName',
    names=<...>,
    *,
    module='...',
    qualname='...',
    type=<mixed-in class>,
    start=1,
    )
  • value:新枚举类将记录为其名称的内容。

  • names:枚举成员。这可以是以空格或逗号分隔的字符串(值将从 1 开始,除非另有指定)

    'RED GREEN BLUE' | 'RED,GREEN,BLUE' | 'RED, GREEN, BLUE'
    

    或名称的迭代器

    ['RED', 'GREEN', 'BLUE']
    

    或(名称,值)对的迭代器

    [('CYAN', 4), ('MAGENTA', 5), ('YELLOW', 6)]
    

    或映射

    {'CHARTREUSE': 7, 'SEA_GREEN': 11, 'ROSEMARY': 42}
    
  • module:新枚举类可以在其中找到的模块名称。

  • qualname:新枚举类可以在模块中的哪个位置找到。

  • type:要混入新枚举类的类型。

  • start:如果仅传递名称,则从哪个数字开始计数。

在 3.5 版本中更改:添加了 start 参数。

派生枚举

IntEnum

提供的 Enum 的第一个变体也是 int 的子类。IntEnum 的成员可以与整数进行比较;通过扩展,不同类型的整数枚举也可以相互比较

>>> from enum import IntEnum
>>> class Shape(IntEnum):
...     CIRCLE = 1
...     SQUARE = 2
...
>>> class Request(IntEnum):
...     POST = 1
...     GET = 2
...
>>> Shape == 1
False
>>> Shape.CIRCLE == 1
True
>>> Shape.CIRCLE == Request.POST
True

但是,它们仍然不能与标准的 Enum 枚举进行比较

>>> class Shape(IntEnum):
...     CIRCLE = 1
...     SQUARE = 2
...
>>> class Color(Enum):
...     RED = 1
...     GREEN = 2
...
>>> Shape.CIRCLE == Color.RED
False

IntEnum 的值在其他方面也表现得像整数一样。

>>> int(Shape.CIRCLE)
1
>>> ['a', 'b', 'c'][Shape.CIRCLE]
'b'
>>> [i for i in range(Shape.SQUARE)]
[0, 1]

StrEnum

提供的 Enum 的第二种变体也是 str 的子类。StrEnum 的成员可以与字符串进行比较;由此,不同类型的字符串枚举也可以相互比较。

3.11 版本新增。

IntFlag

提供的 Enum 的下一种变体 IntFlag,也是基于 int 的。不同之处在于,IntFlag 成员可以使用位运算符(&、|、^、~)进行组合,并且如果可能,结果仍然是 IntFlag 成员。与 IntEnum 一样,IntFlag 成员也是整数,可以在任何使用 int 的地方使用。

注意

除了位运算之外,对 IntFlag 成员的任何操作都会丢失 IntFlag 成员资格。

导致无效 IntFlag 值的位运算将丢失 IntFlag 成员资格。有关详细信息,请参阅 FlagBoundary

3.6 版本新增。

3.11 版本更改。

IntFlag 类的示例

>>> from enum import IntFlag
>>> class Perm(IntFlag):
...     R = 4
...     W = 2
...     X = 1
...
>>> Perm.R | Perm.W
<Perm.R|W: 6>
>>> Perm.R + Perm.W
6
>>> RW = Perm.R | Perm.W
>>> Perm.R in RW
True

也可以命名组合

>>> class Perm(IntFlag):
...     R = 4
...     W = 2
...     X = 1
...     RWX = 7
...
>>> Perm.RWX
<Perm.RWX: 7>
>>> ~Perm.RWX
<Perm: 0>
>>> Perm(7)
<Perm.RWX: 7>

注意

命名的组合被视为别名。别名在迭代期间不会显示,但可以从按值查找返回。

3.11 版本更改。

IntFlagEnum 之间的另一个重要区别是,如果没有设置标志(值为 0),则其布尔值评估为 False

>>> Perm.R & Perm.X
<Perm: 0>
>>> bool(Perm.R & Perm.X)
False

因为 IntFlag 成员也是 int 的子类,它们可以与它们组合(但可能会丢失 IntFlag 成员资格)

>>> Perm.X | 4
<Perm.R|X: 5>

>>> Perm.X + 8
9

注意

取反运算符 ~ 始终返回一个具有正值的 IntFlag 成员

>>> (~Perm.X).value == (Perm.R|Perm.W).value == 6
True

也可以迭代 IntFlag 成员

>>> list(RW)
[<Perm.R: 4>, <Perm.W: 2>]

3.11 版本新增。

Flag

最后一种变体是 Flag。与 IntFlag 类似,Flag 成员可以使用位运算符(&、|、^、~)进行组合。与 IntFlag 不同,它们不能与任何其他 Flag 枚举进行组合或比较,也不能与 int 进行组合或比较。虽然可以直接指定值,但建议使用 auto 作为值,并让 Flag 选择适当的值。

3.6 版本新增。

IntFlag 一样,如果 Flag 成员的组合导致未设置任何标志,则布尔值评估为 False

>>> from enum import Flag, auto
>>> class Color(Flag):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.RED & Color.GREEN
<Color: 0>
>>> bool(Color.RED & Color.GREEN)
False

单个标志应具有 2 的幂的值(1、2、4、8,…),而标志的组合则不会

>>> class Color(Flag):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...     WHITE = RED | BLUE | GREEN
...
>>> Color.WHITE
<Color.WHITE: 7>

为“未设置任何标志”的情况命名不会更改其布尔值

>>> class Color(Flag):
...     BLACK = 0
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.BLACK
<Color.BLACK: 0>
>>> bool(Color.BLACK)
False

也可以迭代 Flag 成员

>>> purple = Color.RED | Color.BLUE
>>> list(purple)
[<Color.RED: 1>, <Color.BLUE: 2>]

3.11 版本新增。

注意

对于大多数新代码,强烈建议使用 EnumFlag,因为 IntEnumIntFlag 打破了枚举的一些语义承诺(通过与整数进行比较,从而通过传递性与其他不相关的枚举进行比较)。IntEnumIntFlag 应该仅在 EnumFlag 不起作用的情况下使用;例如,当整数常量被枚举替换时,或为了与其他系统的互操作性。

其他

虽然 IntEnumenum 模块的一部分,但独立实现它非常简单

class IntEnum(int, ReprEnum):   # or Enum instead of ReprEnum
    pass

这演示了如何定义类似的派生枚举;例如,一个混合了 float 而不是 intFloatEnum

一些规则

  1. 当子类化 Enum 时,混合类型必须在基序列中 Enum 类本身之前出现,如上面的 IntEnum 示例中所示。

  2. 混合类型必须是可子类化的。例如,boolrange 是不可子类化的,如果在 Enum 创建期间用作混合类型,则会引发错误。

  3. 虽然 Enum 可以具有任何类型的成员,但一旦您混合添加其他类型,则所有成员都必须具有该类型的值,例如,上面的 int。此限制不适用于仅添加方法而不指定其他类型的混合。

  4. 当混合添加另一种数据类型时,value 属性枚举成员本身不同,尽管它等效并且将比较相等。

  5. data type 是一个定义 __new__() 的混合,或一个 dataclass

  6. %-样式格式化:%s%r 分别调用 Enum 类的 __str__()__repr__() 方法;其他代码(例如,%i%h 用于 IntEnum)将枚举成员视为其混合类型的成员。

  7. 格式化字符串字面值str.format(),以及 format() 将使用枚举的 __str__() 方法。

注意

因为 IntEnum, IntFlag, 和 StrEnum 被设计为现有常量的直接替代品,它们的 __str__() 方法已被重置为它们数据类型的 __str__() 方法。

何时使用 __new__() vs. __init__()

只要你想自定义 Enum 成员的实际值,就必须使用 __new__()。任何其他的修改都可以放在 __new__()__init__() 中,但最好使用 __init__()

例如,如果你想传递多个项给构造函数,但只想要其中一个作为值

>>> class Coordinate(bytes, Enum):
...     """
...     Coordinate with binary codes that can be indexed by the int code.
...     """
...     def __new__(cls, value, label, unit):
...         obj = bytes.__new__(cls, [value])
...         obj._value_ = value
...         obj.label = label
...         obj.unit = unit
...         return obj
...     PX = (0, 'P.X', 'km')
...     PY = (1, 'P.Y', 'km')
...     VX = (2, 'V.X', 'km/s')
...     VY = (3, 'V.Y', 'km/s')
...

>>> print(Coordinate['PY'])
Coordinate.PY

>>> print(Coordinate(3))
Coordinate.VY

警告

不要 调用 super().__new__(),因为找到的只是用于查找的 __new__;相反,直接使用数据类型。

更精细的点

支持的 __dunder__ 名称

__members__ 是一个只读的有序映射,包含 member_name:member 项。它仅在类上可用。

__new__(),如果指定了,必须创建并返回枚举成员;最好也适当设置成员的 _value_。一旦所有成员被创建,它就不再使用。

支持的 _sunder_ 名称

  • _name_ – 成员的名称

  • _value_ – 成员的值;可以在 __new__ 中设置

  • _missing_() – 当找不到值时使用的查找函数;可以被覆盖

  • _ignore_ – 一个名称列表,可以是 liststr,这些名称不会被转换为成员,并将从最终类中删除

  • _generate_next_value_() – 用于获取枚举成员的适当值;可以被覆盖

  • _add_alias_() – 添加一个新名称作为现有成员的别名。

  • _add_value_alias_() – 添加一个新值作为现有成员的别名。有关示例,请参见 MultiValueEnum

    注意

    对于标准的 Enum 类,选择的下一个值是所见到的最高值加一。

    对于 Flag 类,选择的下一个值将是下一个最高的二的幂。

    在 3.13 版本中更改:之前的版本会使用最后看到的值而不是最高值。

3.6 版本新增:_missing_, _order_, _generate_next_value_

3.7 版本新增:_ignore_

3.13 版本新增:_add_alias_, _add_value_alias_

为了帮助保持 Python 2/Python 3 代码同步,可以提供 _order_ 属性。它将与枚举的实际顺序进行检查,如果两者不匹配,则会引发错误。

>>> class Color(Enum):
...     _order_ = 'RED GREEN BLUE'
...     RED = 1
...     BLUE = 3
...     GREEN = 2
...
Traceback (most recent call last):
...
TypeError: member order does not match _order_:
  ['RED', 'BLUE', 'GREEN']
  ['RED', 'GREEN', 'BLUE']

注意

在 Python 2 代码中,_order_ 属性是必要的,因为在可以记录之前,定义顺序会丢失。

_私有__名称

私有名称 不会转换为枚举成员,但仍然是普通属性。

3.11 版本更改。

Enum 成员类型

枚举成员是其枚举类的实例,通常通过 EnumClass.member 访问。在某些情况下,例如编写自定义枚举行为时,能够直接从一个成员访问另一个成员很有用,并且是支持的;但是,为了避免成员名称与混合类的属性/方法之间发生名称冲突,强烈建议使用大写名称。

在 3.5 版本中更改。

创建与其他数据类型混合的成员

当使用 Enum 对其他数据类型(例如 intstr)进行子类化时,= 之后的所有值都会传递给该数据类型的构造函数。例如

>>> class MyEnum(IntEnum):      # help(int) -> int(x, base=10) -> integer
...     example = '11', 16      # so x='11' and base=16
...
>>> MyEnum.example.value        # and hex(11) is...
17

Enum 类和成员的布尔值

与非 Enum 类型(例如 intstr 等)混合的枚举类会根据混合类型的规则进行评估;否则,所有成员的评估结果均为 True。要使你自己的枚举的布尔评估依赖于成员的值,请将以下内容添加到你的类中

def __bool__(self):
    return bool(self.value)

普通的 Enum 类的评估结果始终为 True

具有方法的 Enum

如果你给你的枚举子类添加额外的方法,如下面的 Planet 类,这些方法将显示在成员的 dir() 中,而不是类的 dir()

>>> dir(Planet)                         
['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__']
>>> dir(Planet.EARTH)                   
['__class__', '__doc__', '__module__', 'mass', 'name', 'radius', 'surface_gravity', 'value']

组合 Flag 的成员

迭代 Flag 成员的组合只会返回由单个位组成的成员

>>> class Color(Flag):
...     RED = auto()
...     GREEN = auto()
...     BLUE = auto()
...     MAGENTA = RED | BLUE
...     YELLOW = RED | GREEN
...     CYAN = GREEN | BLUE
...
>>> Color(3)  # named combination
<Color.YELLOW: 3>
>>> Color(7)      # not named combination
<Color.RED|GREEN|BLUE: 7>

FlagIntFlag 的细节

对我们的示例使用以下代码片段

>>> class Color(IntFlag):
...     BLACK = 0
...     RED = 1
...     GREEN = 2
...     BLUE = 4
...     PURPLE = RED | BLUE
...     WHITE = RED | GREEN | BLUE
...

以下情况属实:

  • 单比特标志是规范的

  • 多比特和零比特标志是别名

  • 迭代期间只返回规范标志

    >>> list(Color.WHITE)
    [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]
    
  • 对标志或标志集取反会返回一个新的标志/标志集,其对应的正整数值

    >>> Color.BLUE
    <Color.BLUE: 4>
    
    >>> ~Color.BLUE
    <Color.RED|GREEN: 3>
    
  • 伪标志的名称由其成员的名称构成

    >>> (Color.RED | Color.GREEN).name
    'RED|GREEN'
    
    >>> class Perm(IntFlag):
    ...     R = 4
    ...     W = 2
    ...     X = 1
    ...
    >>> (Perm.R & Perm.W).name is None  # effectively Perm(0)
    True
    
  • 多比特标志(又称别名)可以从操作中返回

    >>> Color.RED | Color.BLUE
    <Color.PURPLE: 5>
    
    >>> Color(7)  # or Color(-1)
    <Color.WHITE: 7>
    
    >>> Color(0)
    <Color.BLACK: 0>
    
  • 成员资格/包含检查:零值标志始终被视为包含在内

    >>> Color.BLACK in Color.WHITE
    True
    

    否则,只有当一个标志的所有位都包含在另一个标志中时,才会返回 True

    >>> Color.PURPLE in Color.WHITE
    True
    
    >>> Color.GREEN in Color.PURPLE
    False
    

存在一种新的边界机制,用于控制如何处理超出范围/无效的位:STRICTCONFORMEJECTKEEP

  • STRICT –> 当遇到无效值时引发异常

  • CONFORM –> 丢弃任何无效位

  • EJECT –> 失去 Flag 状态,并变为具有给定值的普通 int

  • KEEP –> 保留额外的位

    • 保留 Flag 状态和额外的位

    • 额外的位不会在迭代中显示

    • 额外的位会在 repr() 和 str() 中显示

Flag 的默认值为 STRICTIntFlag 的默认值为 EJECT,而 _convert_ 的默认值为 KEEP(有关何时需要 KEEP 的示例,请参见 ssl.Options)。

枚举和标志有什么不同?

枚举具有自定义元类,该元类会影响派生的 Enum 类及其实例(成员)的许多方面。

枚举类

EnumType 元类负责提供 __contains__()__dir__()__iter__() 和其他方法,这些方法允许人们对 Enum 类执行在典型类上会失败的操作,例如 list(Color)some_enum_var in ColorEnumType 负责确保最终 Enum 类上的各种其他方法是正确的(例如 __new__()__getnewargs__()__str__()__repr__())。

标志类

标志具有更广泛的别名视图:要成为规范的,标志的值必须是 2 的幂值,而不是重复的名称。因此,除了 Enum 的别名定义之外,没有值的标志(又名 0)或具有多个 2 的幂值的标志(例如 3)被认为是别名。

枚举成员(又名实例)

关于枚举成员最有趣的事情是它们是单例。EnumType 在创建枚举类本身时会创建所有成员,然后设置一个自定义的 __new__(),以确保不会实例化任何新的成员,而是仅返回现有成员实例。

标志成员

标志成员可以像 Flag 类一样进行迭代,并且只会返回规范成员。例如

>>> list(Color)
[<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]

(请注意,BLACKPURPLEWHITE 不会显示。)

反转标志成员会返回相应的正值,而不是负值 — 例如

>>> ~Color.RED
<Color.GREEN|BLUE: 6>

标志成员的长度与其包含的 2 的幂值的数量相对应。例如

>>> len(Color.PURPLE)
2

枚举 Cookbook

虽然 EnumIntEnumStrEnumFlagIntFlag 预计将涵盖大多数用例,但它们无法涵盖所有用例。以下是一些不同类型的枚举的示例,可以直接使用,也可以用作创建自己的枚举的示例。

省略值

在许多用例中,人们并不关心枚举的实际值是什么。有几种方法可以定义这种类型的简单枚举

  • 为值使用 auto 的实例

  • 使用 object 的实例作为值

  • 使用描述性字符串作为值

  • 使用元组作为值,并使用自定义的 __new__() 将元组替换为 int

使用这些方法中的任何一种都向用户表明这些值并不重要,并且还可以添加、删除或重新排序成员,而无需重新编号其余成员。

使用 auto

使用 auto 看起来像

>>> class Color(Enum):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.GREEN
<Color.GREEN: 3>

使用 object

使用 object 看起来像

>>> class Color(Enum):
...     RED = object()
...     GREEN = object()
...     BLUE = object()
...
>>> Color.GREEN                         
<Color.GREEN: <object object at 0x...>>

这也是一个很好的示例,说明为什么您可能想要编写自己的 __repr__()

>>> class Color(Enum):
...     RED = object()
...     GREEN = object()
...     BLUE = object()
...     def __repr__(self):
...         return "<%s.%s>" % (self.__class__.__name__, self._name_)
...
>>> Color.GREEN
<Color.GREEN>

使用描述性字符串

使用字符串作为值看起来像

>>> class Color(Enum):
...     RED = 'stop'
...     GREEN = 'go'
...     BLUE = 'too fast!'
...
>>> Color.GREEN
<Color.GREEN: 'go'>

使用自定义的 __new__()

使用自动编号的 __new__() 看起来像

>>> class AutoNumber(Enum):
...     def __new__(cls):
...         value = len(cls.__members__) + 1
...         obj = object.__new__(cls)
...         obj._value_ = value
...         return obj
...
>>> class Color(AutoNumber):
...     RED = ()
...     GREEN = ()
...     BLUE = ()
...
>>> Color.GREEN
<Color.GREEN: 2>

要创建一个更通用的 AutoNumber,请将 *args 添加到签名中

>>> class AutoNumber(Enum):
...     def __new__(cls, *args):      # this is the only change from above
...         value = len(cls.__members__) + 1
...         obj = object.__new__(cls)
...         obj._value_ = value
...         return obj
...

然后,当您从 AutoNumber 继承时,您可以编写自己的 __init__ 来处理任何额外的参数

>>> class Swatch(AutoNumber):
...     def __init__(self, pantone='unknown'):
...         self.pantone = pantone
...     AUBURN = '3497'
...     SEA_GREEN = '1246'
...     BLEACHED_CORAL = () # New color, no Pantone code yet!
...
>>> Swatch.SEA_GREEN
<Swatch.SEA_GREEN: 2>
>>> Swatch.SEA_GREEN.pantone
'1246'
>>> Swatch.BLEACHED_CORAL.pantone
'unknown'

注意

如果定义了 __new__() 方法,它会在枚举成员创建期间使用;然后它会被 Enum 的 __new__() 方法替换,后者用于类创建后查找现有成员。

警告

不要 调用 super().__new__(),因为找到的是只用于查找的 __new__;相反,直接使用数据类型——例如:

obj = int.__new__(cls, value)

OrderedEnum

一个不基于 IntEnum 的有序枚举,因此保持了正常的 Enum 不变量(例如,不能与其他枚举进行比较)

>>> class OrderedEnum(Enum):
...     def __ge__(self, other):
...         if self.__class__ is other.__class__:
...             return self.value >= other.value
...         return NotImplemented
...     def __gt__(self, other):
...         if self.__class__ is other.__class__:
...             return self.value > other.value
...         return NotImplemented
...     def __le__(self, other):
...         if self.__class__ is other.__class__:
...             return self.value <= other.value
...         return NotImplemented
...     def __lt__(self, other):
...         if self.__class__ is other.__class__:
...             return self.value < other.value
...         return NotImplemented
...
>>> class Grade(OrderedEnum):
...     A = 5
...     B = 4
...     C = 3
...     D = 2
...     F = 1
...
>>> Grade.C < Grade.A
True

DuplicateFreeEnum

如果找到重复的成员值,则引发错误,而不是创建别名

>>> class DuplicateFreeEnum(Enum):
...     def __init__(self, *args):
...         cls = self.__class__
...         if any(self.value == e.value for e in cls):
...             a = self.name
...             e = cls(self.value).name
...             raise ValueError(
...                 "aliases not allowed in DuplicateFreeEnum:  %r --> %r"
...                 % (a, e))
...
>>> class Color(DuplicateFreeEnum):
...     RED = 1
...     GREEN = 2
...     BLUE = 3
...     GRENE = 2
...
Traceback (most recent call last):
  ...
ValueError: aliases not allowed in DuplicateFreeEnum:  'GRENE' --> 'GREEN'

注意

这是一个有用的示例,用于子类化 Enum 以添加或更改其他行为,以及禁止别名。如果唯一需要的更改是禁止别名,则可以使用 unique() 装饰器代替。

MultiValueEnum

支持每个成员具有多个值

>>> class MultiValueEnum(Enum):
...     def __new__(cls, value, *values):
...         self = object.__new__(cls)
...         self._value_ = value
...         for v in values:
...             self._add_value_alias_(v)
...         return self
...
>>> class DType(MultiValueEnum):
...     float32 = 'f', 8
...     double64 = 'd', 9
...
>>> DType('f')
<DType.float32: 'f'>
>>> DType(9)
<DType.double64: 'd'>

Planet

如果定义了 __new__()__init__(),枚举成员的值将被传递给这些方法

>>> class Planet(Enum):
...     MERCURY = (3.303e+23, 2.4397e6)
...     VENUS   = (4.869e+24, 6.0518e6)
...     EARTH   = (5.976e+24, 6.37814e6)
...     MARS    = (6.421e+23, 3.3972e6)
...     JUPITER = (1.9e+27,   7.1492e7)
...     SATURN  = (5.688e+26, 6.0268e7)
...     URANUS  = (8.686e+25, 2.5559e7)
...     NEPTUNE = (1.024e+26, 2.4746e7)
...     def __init__(self, mass, radius):
...         self.mass = mass       # in kilograms
...         self.radius = radius   # in meters
...     @property
...     def surface_gravity(self):
...         # universal gravitational constant  (m3 kg-1 s-2)
...         G = 6.67300E-11
...         return G * self.mass / (self.radius * self.radius)
...
>>> Planet.EARTH.value
(5.976e+24, 6378140.0)
>>> Planet.EARTH.surface_gravity
9.802652743337129

TimePeriod

一个展示 _ignore_ 属性用法的示例

>>> from datetime import timedelta
>>> class Period(timedelta, Enum):
...     "different lengths of time"
...     _ignore_ = 'Period i'
...     Period = vars()
...     for i in range(367):
...         Period['day_%d' % i] = i
...
>>> list(Period)[:2]
[<Period.day_0: datetime.timedelta(0)>, <Period.day_1: datetime.timedelta(days=1)>]
>>> list(Period)[-2:]
[<Period.day_365: datetime.timedelta(days=365)>, <Period.day_366: datetime.timedelta(days=366)>]

子类化 EnumType

虽然大多数枚举需求可以通过自定义 Enum 子类(使用类装饰器或自定义函数)来满足,但可以子类化 EnumType 来提供不同的 Enum 体验。