Enum 指南

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 的类。

备注

枚举成员名的大小写

由于枚举被用来代表常量,并有助于避免混入类方法/属性和枚举名之间发生名称冲突,我们强烈建议用大写形式的名称表示成员,我们将在我们的示例中使用此风格。

根据枚举的性质,某个成员的值可能不一定用得上,但无论如何都能用那个值构造对应的成员:

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

Unlike many languages that treat enumerations solely as name/value pairs, Python Enums can have behavior added. For example, datetime.date has two methods for returning the weekday: weekday() and isoweekday(). The difference is that one of them counts from 0-6 and the other from 1-7. Rather than keep track of that ourselves we can add a method to the Weekday enum to extract the day from the date instance and return the matching enum member:

@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>

当然,如果换个日子读到这篇文章,应该看到当天是周几。

This Weekday enum is great if our variable only needs one day, but what if we need several? Maybe we're writing a function to plot chores during a week, and don't want to use a list -- we could use a different type of Enum:

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

这里做了两处改动:继承了 Flag,而且值都是2的幂。

Just like the original Weekday enum above, we can have a single selection:

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

If you have an enum member and need its name or value:

>>> 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。按名称检索 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

比较运算

枚举成员是按 ID 进行比较的:

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

警告

重载模块是可能的 -- 如果载入的模块包含枚举,它们将被重新创建,而新成员的标识号/相等性比较不一定会通过。

合法的枚举成员和属性

以上大多数示例都用了整数作为枚举值。使用整数确实简短方便(并且是 Functional API 默认提供的值),但并非强制要求。绝大多数情况下,人们并不关心枚举的实际值是什么。但如果值确实重要,可以使用任何值。

枚举是 Python 的类,可带有普通方法和特殊方法。假设有如下枚举:

>>> class Mood(Enum):
...     FUNKY = 1
...     HAPPY = 3
...
...     def describe(self):
...         # 在这里 self 是成员
...         return self.name, self.value
...
...     def __str__(self):
...         return 'my custom str! {0}'.format(self.value)
...
...     @classmethod
...     def favorite_mood(cls):
...         # 在这里 cls 是枚举
...         return cls.HAPPY
...

那么:

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

The rules for what is allowed are as follows: names that start and end with a single underscore are reserved by enum and cannot be used; all other attributes defined within an enumeration will become members of this enumeration, with the exception of special methods (__str__(), __add__(), etc.), descriptors (methods are also descriptors), and variable names listed in _ignore_.

Note: if your enumeration defines __new__() and/or __init__(), any value(s) given to the enum member will be passed into those methods. See Planet for an example.

备注

The __new__() method, if defined, is used during creation of the Enum members; it is then replaced by Enum's __new__() which is used after class creation for lookup of existing members. See When to use __new__() vs. __init__() for more details.

受限的 Enum 子类化

新建的 Enum 类必须包含:一个枚举基类、至多一种数据类型和按需提供的基于 object 的混合类。这些基类的顺序如下:

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 版本发生变更: 只有数据类字段会被显示在值区域中,而不会显示数据类的名称。Only the dataclass fields are shown in the value area, not the dataclass' name.

备注

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

>>> @dataclass               # 不要这样做:没有任何意义
... class Color(Enum):
...    RED = 1
...    BLUE = 2
...
>>> Color.RED is Color.BLUE
False
>>> Color.RED == Color.BLUE  # 问题在这里:它们不应该相等
True

打包(pickle)

枚举类型可以被打包和解包:

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

打包的常规限制同样适用于枚举类型:必须在模块的最高层级定义,因为解包操作要求可从该模块导入。

备注

用 pickle 协议版本 4 可以轻松地将嵌入其他类中的枚举进行打包。

It is possible to modify how enum members are pickled/unpickled by defining __reduce_ex__() in the enumeration class. The default method is by-value, but enums with complicated values may want to use by-name:

>>> 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 的语义类似于 namedtuple。调用 Enum 的第一个参数是枚举的名称。

The second argument is the source of enumeration member names. It can be a whitespace-separated string of names, a sequence of names, a sequence of 2-tuples with key/value pairs, or a mapping (e.g. dictionary) of names to values. The last two options enable assigning arbitrary values to enumerations; the others auto-assign increasing integers starting with 1 (use the start parameter to specify a different starting value). A new class derived from Enum is returned. In other words, the above assignment to Animal is equivalent to:

>>> 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 成员将不可被解封;为了让错误尽量靠近源头,封存将被禁用。

The new pickle protocol 4 also, in some circumstances, relies on __qualname__ being set to the location where pickle will be able to find the class. For example, if the class was made available in class SomeData in the global scope:

>>> 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 的成员可与字符串进行比较;通过扩展,不同类型的字符串枚举也可以相互进行比较。

Added in version 3.11.

IntFlag

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

备注

除位操作外,其他所有对 IntFlag 成员的操作,都会失去 IntFlag 成员资格。

导致 IntFlag 值无效的位操作将失去 IntFlag 成员资格。详见 FlagBoundary

Added in version 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>]

Added in version 3.11.

标志

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

Added in version 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

单个旗标的值应当为二的乘方 (1, 2, 4, 8, ...),而旗标的组合则无此限制:

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

对 "no flags set" 条件指定一个名称并不会改变其布尔值:

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

Added in version 3.11.

备注

对于大多数新代码,强烈推荐使用 EnumFlag,因为 IntEnumIntFlag 打破了枚举的某些语义约定(例如可以同整数进行比较,并因而导致此行为被传递给其他无关的枚举)。 IntEnumIntFlag 的使用应当仅限于 EnumFlag 无法使用的场合;例如,当使用枚举替代整数常量时,或是与其他系统进行交互操作时。

其他事项

虽然 IntEnumenum 模块的一部分,但要独立实现也应该相当容易:

class IntEnum(int, ReprEnum):   # 或用 Enum 而不是 ReprEnum
    pass

This demonstrates how similar derived enumerations can be defined; for example a FloatEnum that mixes in float instead of int.

几条规则:

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

  2. 混入类型必须是可子类化的。 例如,boolrange 是不可子类化的因而如果被用作混入类型就将在枚举创建期间抛出错误。

  3. 虽然 Enum 可以拥有任意类型的成员,不过一旦你混合了附加类型,则所有成员必须为相应类型的值,如在上面的例子中即为 int。 此限制不适用于仅添加方法而未指定另一数据类型的混合类。

  4. When another data type is mixed in, the value attribute is not the same as the enum member itself, although it is equivalent and will compare equal.

  5. A data type is a mixin that defines __new__(), or a dataclass

  6. %-style formatting: %s and %r call the Enum class's __str__() and __repr__() respectively; other codes (such as %i or %h for IntEnum) treat the enum member as its mixed-in type.

  7. Formatted string literals, str.format(), and format() will use the enum's __str__() method.

备注

Because IntEnum, IntFlag, and StrEnum are designed to be drop-in replacements for existing constants, their __str__() method has been reset to their data types' __str__() method.

When to use __new__() vs. __init__()

__new__() must be used whenever you want to customize the actual value of the Enum member. Any other modifications may go in either __new__() or __init__(), with __init__() being preferred.

举例来说,如果你要向构造器传入多个条目,但只希望将其中一个作为值:

>>> 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__ is a read-only ordered mapping of member_name:member items. It is only available on the class.

__new__(), if specified, must create and return the enum members; it is also a very good idea to set the member's _value_ appropriately. Once all the members are created it is no longer used.

支持的 _sunder_ 名称

  • _name_ -- 成员的名称

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

  • _missing_() -- 当未找到某个值时所使用的查找函数;可被重写

  • _ignore_ -- 一个名称列表,可以为 liststr,它不会被转化为成员,并将从最终类中移除

  • _generate_next_value_() -- 用于为枚举成员获取适当的值;可被重写

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

  • _add_value_alias_() -- adds a new value as an alias to an existing member. See MultiValueEnum for an example.

    备注

    对于标准的 Enum 类来说下一个被选择的值将是已有的最高值加一。

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

    在 3.13 版本发生变更: 在之前版本中将会使用最近的值而不是最高的值。

Added in version 3.6: _missing_, _order_, _generate_next_value_

Added in version 3.7: _ignore_

Added in version 3.13: _add_alias_, _add_value_alias_

To help keep Python 2 / Python 3 code in sync an _order_ attribute can be provided. It will be checked against the actual order of the enumeration and raise an error if the two do not match:

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

备注

In Python 2 code the _order_ attribute is necessary as definition order is lost before it can be recorded.

_Private__names

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

在 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(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
...

下列情况为True:

  • 单比特标志是典型的

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

  • 迭代过程中只返回典型的标志:

    >>> 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()中确实显示了额外的比特

默认的标志为 STRICTIntFlag``默认为``EJECT_convert_``默认为``KEEP``(需要``KEEP``的例子见``ssl.Options)。

枚举和旗标有何差异?

Enum有一个自定义的元类,它影响到派生的 Enum 类和它们的实例(成员)的许多方面。

枚举类

The EnumType metaclass is responsible for providing the __contains__(), __dir__(), __iter__() and other methods that allow one to do things with an Enum class that fail on a typical class, such as list(Color) or some_enum_var in Color. EnumType is responsible for ensuring that various other methods on the final Enum class are correct (such as __new__(), __getnewargs__(), __str__() and __repr__()).

旗标类

旗标具有扩展的别名视图:为了符合规范,旗标的值必须为二的乘方,且名称不可重复。 因此,除了别名的定义 Enum 之外,没有值 (即 0) 或是几个二的乘方值之和 (如 3) 的旗标也会被视为别名。

枚举成员(即实例)

The most interesting thing about enum members is that they are singletons. EnumType creates them all while it is creating the enum class itself, and then puts a custom __new__() in place to ensure that no new ones are ever instantiated by returning only the existing member instances.

旗标成员

旗标成员可以如 Flag 类一样被迭代,并且只有规范的成员会被返回。 例如:

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

(请注意 BLACK, PURPLEWHITE 将不显示。)

对一个旗标成员取反将返回对应的正值,而不是负值 --- 例如:

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

旗标成员具有与它们所包含的二的乘方值的数量相对应的长度。 例如:

>>> len(Color.PURPLE)
2

枚举指导手册

虽然 Enum, IntEnum, StrEnum, FlagIntFlag 有望能涵盖大多数的使用情况,但它们不能涵盖所有情况。 这里有一些不同类型的枚举的方法,可以直接使用,或者作为创建定制枚举的范例。

省略值

在许多应用场景中,人们并不关心枚举的实际值是什么。 有几种方式可用来定义这种类型的简单枚举:

  • 使用 auto 的实例作为值

  • 使用 object 的实例作为值

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

  • use a tuple as the value and a custom __new__() to replace the tuple with an int value

使用以上任何一种方法均可向用户指明值并不重要,并且使人能够添加、移除或重排序成员而不必改变其余成员的数值。

使用 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...>>

This is also a good example of why you might want to write your own __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'>

Using a custom __new__()

Using an auto-numbering __new__() would look like:

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

备注

The __new__() method, if defined, is used during creation of the Enum members; it is then replaced by Enum's __new__() which is used after class creation for lookup of existing members.

警告

不要 调用 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

If __new__() or __init__() is defined, the value of the enum member will be passed to those methods:

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

An example to show the _ignore_ attribute in use:

>>> 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 可以被子类化以提供不同的枚举体验。