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
然而,一个枚举成员可以关联多个其他名称。如果两个枚举项 A 和 B 具有相同值(并且首先定义的是 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_SQUARE 和 Weekday.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 的地方。
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 版本发生变更.
IntFlag 和 Enum 的另一个重要区别在于如果没有设置任何旗标(值为 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 成员也可被迭代遍历:
>>> 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.
其他事项¶
虽然 IntEnum 是 enum 模块的一部分,但要独立实现也应该相当容易:
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.
几条规则:
混入类型必须是可子类化的。 例如,
bool和range是不可子类化的因而如果被用作混入类型就将在枚举创建期间抛出错误。虽然
Enum可以拥有任意类型的成员,不过一旦你混合了附加类型,则所有成员必须为相应类型的值,如在上面的例子中即为int。 此限制不适用于仅添加方法而未指定另一数据类型的混合类。When another data type is mixed in, the
valueattribute is not the same as the enum member itself, although it is equivalent and will compare equal.A
data typeis a mixin that defines__new__(), or adataclass%-style formatting:
%sand%rcall theEnumclass's__str__()and__repr__()respectively; other codes (such as%ior%hfor IntEnum) treat the enum member as its mixed-in type.Formatted string literals,
str.format(), andformat()will use the enum's__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_()-- 当未找到某个值时所使用的查找函数;可被重写_generate_next_value_()-- 用于为枚举成员获取适当的值;可被重写_add_alias_()-- 添加一个新名称作为现有成员的别名。_add_value_alias_()-- adds a new value as an alias to an existing member. See MultiValueEnum for an example.在 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 来子类化其他数据类型,如 int 或 str 时,所有在 = 之后的值都会被传递给该数据类型的构造器。 例如:
>>> 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 类型(如 int、str 等)混合的枚举类会根据混合类型的规则进行计算;否则,所有成员都计算为 True。为了使你自己的枚举的布尔值取决于成员的值,请在你的类中添加以下内容:
def __bool__(self):
return bool(self.value)
带有方法的 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>
Flag 和 IntFlag 的细节¶
使用以下代码段作为我们的例子:
>>> 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
有一个新的边界机制,控制如何处理超出范围的/无效的比特:STRICT,CONFORM,EJECT,KEEP。
STRICT --> 当出现无效的值时,会触发一个异常。
CONFORM --> 丢弃任何无效的比特
EJECT --> 失去Flag的状态,成为一个普通的int,其值为给定值。
KEEP --> 保留额外的比特
保留Flag状态和额外的比特
额外的比特不会在迭代中显示出来
在repr()和str()中确实显示了额外的比特
默认的标志为 STRICT,IntFlag``默认为``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, PURPLE 和 WHITE 将不显示。)
对一个旗标成员取反将返回对应的正值,而不是负值 --- 例如:
>>> ~Color.RED
<Color.GREEN|BLUE: 6>
旗标成员具有与它们所包含的二的乘方值的数量相对应的长度。 例如:
>>> len(Color.PURPLE)
2
枚举指导手册¶
虽然 Enum, IntEnum, StrEnum, Flag 和 IntFlag 有望能涵盖大多数的使用情况,但它们不能涵盖所有情况。 这里有一些不同类型的枚举的方法,可以直接使用,或者作为创建定制枚举的范例。
省略值¶
在许多应用场景中,人们并不关心枚举的实际值是什么。 有几种方式可用来定义这种类型的简单枚举:
使用
auto的实例作为值使用
object的实例作为值使用描述性的字符串作为值
use a tuple as the value and a custom
__new__()to replace the tuple with anintvalue
使用以上任何一种方法均可向用户指明值并不重要,并且使人能够添加、移除或重排序成员而不必改变其余成员的数值。
使用 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 可以被子类化以提供不同的枚举体验。