枚举 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>
如果您有一个枚举成员并且需要它的 name
或 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
。按名称查找 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
。
比较操作¶
枚举成员通过身份进行比较
>>> 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 的语义类似于 namedtuple
。Enum
调用的第一个参数是枚举的名称。
第二个参数是枚举成员名称的 来源。它可以是空格分隔的名称字符串、名称序列、包含键/值对的 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 版中已更改。
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>]
在 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 版本中新增。
其他¶
虽然 IntEnum
是 enum
模块的一部分,但独立实现它会非常简单
class IntEnum(int, ReprEnum): # or Enum instead of ReprEnum
pass
这演示了如何定义类似的派生枚举;例如,一个混合了 float
而不是 int
的 FloatEnum
。
一些规则
子类化
Enum
时,mixin 类型必须出现在基类序列中的Enum
类本身之前,如上面的IntEnum
示例所示。混合类型必须是可子类化的。例如,
bool
和range
不可子类化,如果在 Enum 创建时用作混合类型,将引发错误。虽然
Enum
可以有任何类型的成员,但一旦混合了额外类型,所有成员的值都必须是该类型,例如上面是int
。此限制不适用于仅添加方法而不指定其他类型的混合。当混合其他数据类型时,
value
属性与枚举成员本身 不同,尽管它们等价并且会比较相等。%-风格格式:
%s
和%r
分别调用Enum
类的__str__()
和__repr__()
;其他代码(例如 IntEnum 的%i
或%h
)将枚举成员视为其混合类型。格式化字符串字面量、
str.format()
和format()
将使用枚举的__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_()
– 当找不到值时使用的查找函数;可以被覆盖_generate_next_value_()
– 用于获取枚举成员的适当值;可以被覆盖_add_alias_()
– 将新名称添加为现有成员的别名。_add_value_alias_()
– 将新值添加为现有成员的别名。有关示例,请参见 MultiValueEnum。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 版本中的变化。
创建与其他数据类型混合的成员¶
当将其他数据类型(例如 int
或 str
)与 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
类型(例如 int
、str
等)混合的枚举类根据混合类型的规则进行评估;否则,所有成员都评估为 True
。要使您自己的枚举的布尔评估取决于成员的值,请将以下内容添加到您的类中
def __bool__(self):
return bool(self.value)
带方法的 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>
Flag
和 IntFlag
细节¶
使用以下代码片段作为我们的示例
>>> 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
有一个新的边界机制来控制如何处理超出范围/无效的位:STRICT
、CONFORM
、EJECT
和 KEEP
STRICT –> 遇到无效值时引发异常
CONFORM –> 丢弃任何无效位
EJECT –> 失去 Flag 状态并成为具有给定值的普通整数
KEEP –> 保留额外的位
保留 Flag 状态和额外的位
额外的位不会出现在迭代中
额外的位会出现在 repr() 和 str() 中
Flag 的默认值为 STRICT
,IntFlag
的默认值为 EJECT
,_convert_
的默认值为 KEEP
(请参阅 ssl.Options
以获取何时需要 KEEP
的示例)。
枚举和标志有何不同?¶
枚举具有自定义的元类,它影响派生 Enum
类及其实例(成员)的许多方面。
枚举类¶
EnumType
元类负责提供 __contains__()
、__dir__()
、__iter__()
以及其他允许人们对 Enum
类执行在典型类上会失败的操作的方法,例如 list(Color)
或 some_enum_var in Color
。EnumType
负责确保最终 Enum
类上的其他各种方法是正确的(例如 __new__()
、__getnewargs__()
、__str__()
和 __repr__()
)。
标志类¶
标志具有扩展的别名视图:要成为规范的,标志的值需要是 2 的幂值,而不是重复的名称。因此,除了 Enum
定义的别名之外,没有值(又名 0
)或具有多个 2 的幂值(例如 3
)的标志都被视为别名。
枚举成员(即实例)¶
枚举成员最有趣的地方在于它们是单例。 EnumType
在创建枚举类本身时就创建了所有成员,然后设置了一个自定义的 __new__()
来确保通过只返回现有成员实例,永远不会实例化新的成员。
标志成员¶
标志成员可以像 Flag
类一样迭代,并且只返回规范成员。例如
>>> list(Color)
[<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]
(请注意,BLACK
、PURPLE
和 WHITE
没有出现。)
反转标志成员会返回相应的正值,而不是负值——例如
>>> ~Color.RED
<Color.GREEN|BLUE: 6>
标志成员的长度对应于它们包含的 2 的幂值的数量。例如
>>> len(Color.PURPLE)
2
枚举食谱¶
虽然 Enum
、IntEnum
、StrEnum
、Flag
和 IntFlag
预计可以涵盖大多数用例,但它们无法涵盖所有用例。这里有一些不同类型枚举的食谱,可以直接使用,也可以作为创建自己枚举的示例。
省略值¶
在许多用例中,人们并不关心枚举的实际值是什么。有几种方法可以定义这种简单的枚举
使用这些方法中的任何一种都向用户表明这些值并不重要,并且还允许添加、删除或重新排序成员,而无需重新编号剩余成员。
使用 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'
警告
不要 调用 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
以提供不同的枚举体验。