枚举 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 的类一样简单。

备注

枚举成员的大小写

由于枚举用于表示常量,并且为了帮助避免 mixin 类方法/属性与枚举名称之间的名称冲突问题,我们强烈建议对成员使用大写名称,并且在我们的示例中将使用这种风格。

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

>>> 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__() 方法,则在创建枚举成员期间使用;然后它会被枚举的 __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 支持

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

Pickling

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

>>> 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']
    

    或 (name, value) 对的迭代器

    [('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 时,mixin 类型必须出现在基类序列中的 Enum 类本身之前,如上面的 IntEnum 示例所示。

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

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

  4. 当混合其他数据类型时,value 属性与枚举成员本身 不同,尽管它们等价并且会比较相等。

  5. 一个 data type 是一个定义了 __new__() 的 mixin,或者是一个 dataclass

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

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

备注

由于 IntEnumIntFlagStrEnum 被设计为现有常量的直接替代品,它们的 __str__() 方法已重置为其数据类型的 __str__() 方法。

何时使用 __new__() 而非 __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 类,选择的下一个值将是下一个最大的 2 的幂。

    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_ 属性是必要的,因为在记录之前定义顺序会丢失。

_Private__names

私有名称 不会转换为枚举成员,而是保持普通属性。

3.11 版中已更改。

Enum 成员类型

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

3.5 版本中的变化。

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

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

>>> 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 状态并成为具有给定值的普通整数

  • KEEP –> 保留额外的位

    • 保留 Flag 状态和额外的位

    • 额外的位不会出现在迭代中

    • 额外的位会出现在 repr() 和 str() 中

Flag 的默认值为 STRICTIntFlag 的默认值为 EJECT_convert_ 的默认值为 KEEP(请参阅 ssl.Options 以获取何时需要 KEEP 的示例)。

枚举和标志有何不同?

枚举具有自定义的元类,它影响派生 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

枚举食谱

虽然 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__() 方法,则在创建枚举成员期间使用;然后它会被枚举的 __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 以提供不同的枚举体验。