8.13. enum - 支持枚举

版本3.4中的新功能。

源代码: Lib/enum.py

枚举是一组绑定到唯一的常量值的符号名(成员)。在枚举中,可以通过身份来比较成员,并且可以迭代枚举本身。

8.13.1.模块内容

此模块定义了两个枚举类,可用于定义唯一的名称和值集:EnumIntEnum它还定义了一个装饰器unique()

class enum.Enum

用于创建枚举常量的基类。有关替代构造语法,请参见Functional API一节。

class enum.IntEnum

用于创建枚举常量的基类,也是int的子类。

enum.unique()

枚举类装饰器,确保只有一个名称绑定到任何一个值。

8.13.2.创建枚举

枚举是使用class语法创建的,这使得它们易于读写。Functional API中描述了另一种创建方法。要定义枚举,子类Enum如下:

>>> from enum import Enum
>>> class Color(Enum):
...     red = 1
...     green = 2
...     blue = 3
...

注意

命名

  • Color枚举(或枚举
  • 属性Color.redColor.green等是枚举成员(或枚举成员)。
  • 枚举成员具有名称Color.red的名称为red,值Color.blue3等)

注意

即使我们使用class语法创建枚举,枚举不是正常的Python类。请参阅枚举如何不同?更多细节。

枚举成员具有可读的字符串表示形式:

>>> print(Color.red)
Color.red

...而他们的repr有更多信息:

>>> print(repr(Color.red))
<Color.red: 1>

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

>>> type(Color.red)
<enum 'Color'>
>>> isinstance(Color.green, Color)
True
>>>

枚举成员还具有只包含其项目名称的属性:

>>> print(Color.red.name)
red

枚举按定义顺序支持迭代:

>>> class Shake(Enum):
...     vanilla = 7
...     chocolate = 4
...     cookies = 9
...     mint = 3
...
>>> for shake in Shake:
...     print(shake)
...
Shake.vanilla
Shake.chocolate
Shake.cookies
Shake.mint

枚举成员是可哈希的,因此它们可以在字典和集合中使用:

>>> apples = {}
>>> apples[Color.red] = 'red delicious'
>>> apples[Color.green] = 'granny smith'
>>> apples == {Color.red: 'red delicious', Color.green: 'granny smith'}
True

8.13.3.对枚举成员及其属性的编程访问

有时,以编程方式访问枚举中的成员是有用的。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

8.13.4.复制枚举成员和值

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

>>> class Shape(Enum):
...     square = 2
...     square = 3
...
Traceback (most recent call last):
...
TypeError: Attempted to reuse key: 'square'

但是,两个枚举成员允许具有相同的值。给定两个成员A和B具有相同的值(并且A首先定义),B是A的别名。A和B的值的值的查找将返回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>

注意

尝试创建与已定义属性(另一个成员,方法等)具有相同名称的成员,或者尝试创建与成员具有相同名称的属性。

8.13.5.确保唯一枚举值

默认情况下,枚举允许多个名称作为同一值的别名。当不需要此行为时,可以使用以下装饰器来确保每个值在枚举中仅使用一次:

@enum.unique

专用于枚举的class装饰器。它搜索枚举的__members__收集找到的任何别名;如果发现任何ValueError引发的细节:

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

8.13.6.迭代

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

>>> list(Shape)
[<Shape.square: 2>, <Shape.diamond: 1>, <Shape.circle: 3>]

特殊属性__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']

8.13.7.比较

枚举成员通过身份进行比较:

>>> 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: unorderable types: Color() < Color()

然而相等比较是已经定义了的︰

>>> Color.blue == Color.red
False
>>> Color.blue != Color.red
True
>>> Color.blue == Color.blue
True

与非枚举类型的比较结果始终是不相等的 (再次,IntEnum 被显式设计的有不同表现,见下文)︰

>>> Color.blue == 2
False

8.13.8.允许枚举的成员和属性

上面的示例使用整数作为枚举值。使用整数是简短且方便的(默认情况下由Functional 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__()等)和描述器(方法也是描述器)。

注意:如果枚举定义__new__()和/或__init__(),那么枚举成员的任何值都将传递到这些方法中。有关示例,请参阅Planet

8.13.9.枚举的子类化

只有枚举没有定义任何成员时,才允许子类化枚举。所以这是禁止的:

>>> class MoreColor(Color):
...     pink = 17
...
Traceback (most recent call last):
...
TypeError: Cannot extend enumerations

但这是允许的:

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

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

8.13.10.Pickling

枚举可以被pickled和unpickled:

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

酸洗的通常限制适用:pickleable枚举必须在模块的顶层定义,因为unpickling要求它们可从该模块导入。

注意

使用pickle协议版本4可以很容易地腌排嵌套在其他类中的枚举。

可以通过定义枚举类中的__reduce_ex__()来修改枚举成员如何进行pickle / unpickled。

8.13.11.函数API

Enum类是可调用的,提供以下功能API:

>>> Animal = Enum('Animal', 'ant bee cat dog')
>>> Animal
<enum 'Animal'>
>>> Animal.ant
<Animal.ant: 1>
>>> Animal.ant.value
1
>>> list(Animal)
[<Animal.ant: 1>, <Animal.bee: 2>, <Animal.cat: 3>, <Animal.dog: 4>]

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

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

>>> class Animal(Enum):
...     ant = 1
...     bee = 2
...     cat = 3
...     dog = 4
...

默认为1作为起始号而不是0的原因是,布尔意义上的0False ,但枚举成员都计算为True

使用功能API创建的pickling枚举可能很棘手,因为帧堆栈实现细节用于尝试和找出枚举创建在哪个模块。如果在单独的模块中使用实用程序函数,它也会失败,并且也可能无法在IronPython或Jython上工作)。解决方案是明确指定模块名称如下:

>>> Animal = Enum('Animal', 'ant bee cat dog', module=__name__)

警告

如果未提供module,并且Enum无法确定它是什么,则新的Enum成员将不会取消拆分;为了使错误更接近源,pickling将被禁用。

在某些情况下,新的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)
值:

新的Enum类将记录为它的名称。

名称:

枚举成员。这可以是空格或逗号分隔的字符串(除非另有说明,否则值将从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}
模块:

模块的名称,其中可以找到新的Enum类。

qualname:

其中在module可以找到新的Enum类。

类型:

类型混合到新的Enum类中。

开始:

如果只传递名称,则开始计数的数字。

在版本3.5中已更改:添加了开始参数。

8.13.12.派生枚举

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

对于绝大多数代码,强烈建议使用Enum,因为IntEnum打破了枚举的某些语义承诺(通过与整数相比较,并因此与其他无关的枚举)。它应该仅在没有其他选择的特殊情况下使用;例如,当整数常量被枚举替代,并且向后兼容性需要仍然期望整数的代码。

8.13.12.2.其他

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

class IntEnum(int, Enum):
    pass

这说明如何定义类似的派生枚举;例如在str而不是int中混合的StrEnum

一些规则:

  1. 当子类化Enum时,混合类型必须在基本序列中的Enum之前出现,如上面的IntEnum示例。
  2. 虽然Enum可以包含任何类型的成员,但一旦您混合了其他类型,所有成员必须具有该类型的值。int此限制不适用于仅添加方法且不指定其他数据类型(例如intstr)的混合。
  3. 当混合其他数据类型时,value属性与枚举成员本身不同,虽然它是等效的,并且将比较相等。
  4. %格式:%s%r调用Enum类的__str__()__repr__();其他代码(例如IntEnum的%i%h)将枚举成员视为其混合类型。
  5. str.format()(或format())将使用混合类型的__format__()如果需要Enum类的str()repr(),请使用!s!r格式代码。

8.13.13.有趣的例子

虽然EnumIntEnum预期覆盖大多数用例,但它们无法覆盖所有这些用例。这里是一些不同类型的枚举的方法,可以直接使用,或者作为创建自己的例子。

8.13.13.1.自动编号

避免必须为每个枚举成员指定值:

>>> 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.value == 2
True

注意

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

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

8.13.13.3.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()装饰器。

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

8.13.14.如何枚举不同?

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

8.13.14.1.枚举类

The EnumMeta 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_var in Color. EnumMeta负责确保最终Enum类上的各种其他方法正确(例如__new__()__getnewargs__()__str__()__repr__())。

8.13.14.2.枚举成员(又名实例)

Enum成员最有趣的是它们是单例。EnumMeta在创建Enum类时创建它们,然后放置自定义__new__(),以确保没有新的通过仅返回现有成员实例来实例化。

8.13.14.3.精细点

Enum成员是Enum类的实例,即使它们可作为EnumClass.member访问,它们不应直接从成员访问因为该查找可能会失败,或者更糟的是,返回除了您正在寻找的Enum成员之外的东西:

>>> class FieldTypes(Enum):
...     name = 0
...     value = 1
...     size = 2
...
>>> FieldTypes.value.size
<FieldTypes.size: 2>
>>> FieldTypes.size.value
2

在版本3.5中更改。

__members__属性仅在类上可用。

如果您给予Enum子类额外方法,如上述Planet类,那些方法将显示在成员的dir()

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

__new__()方法将仅用于创建Enum成员 - 在被替换之后。任何自定义__new__()方法都必须创建对象,并相应地设置_value_属性。

如果你想改变Enum成员的查找方式,你应该为Enum子类编写一个辅助函数或classmethod()