enum —- 对枚举的支持

3.4 新版功能.

源代码:Lib/enum.py


枚举是一组符号名称(枚举成员)的集合,枚举成员应该是唯一的、不可变的。在枚举中,可以对成员进行恒等比较,并且枚举本身是可迭代的。

模块内容

此模块定义了四个枚举类,它们可被用来定义名称和值的不重复集合: Enum, IntEnum, FlagIntFlag。 此外还定义了一个装饰器 unique() 和一个辅助类 auto

  • class enum.Enum
  • 此基类用于创建枚举常量。 请参阅 Functional API 小节了解另一种替代性的构建语法。

  • class enum.IntEnum

  • 此基类用于创建属于 int 的子类的枚举常量。

  • class enum.IntFlag

  • 此基类用于创建可使用按位运算符进行组合而不会丢失其 IntFlag 成员资格的枚举常量。 IntFlag 成员同样也是 int 的子类。

  • class enum.Flag

  • 此基类用于创建枚举常量 可使用按位运算符进行组合而不会丢失其 Flag 成员资格的枚举常量。

  • enum.unique()

  • 此 Enum 类装饰器可确保只将一个名称绑定到任意一个值。

  • class enum.auto

  • 实例会被替换为一个可用作 Enum 成员的正确值。

3.6 新版功能: Flag, IntFlag, auto

创建一个 Enum

枚举是使用 class 语法来创建的,这使得它们易于读写。 另一种替代创建方法的描述见 Functional API。 要定义一个枚举,可以对 Enum 进行如下的子类化:

  1. >>> from enum import Enum
  2. >>> class Color(Enum):
  3. ... RED = 1
  4. ... GREEN = 2
  5. ... BLUE = 3
  6. ...

注解

Enum 的成员值

成员值可以为任意类型: int, str 等等。 如果具体的值不重要,你可以使用 auto 实例,将为你选择适当的值。 但如果你混用 auto 与其他值则需要小心谨慎。

注解

命名法

  • Color 是一个 枚举 (或称 enum)

  • 属性 Color.RED, Color.GREEN 等等是 枚举成员 (或称 enum 成员) 并且被用作常量。

  • 枚举成员具有 名称 (Color.RED 的名称为 REDColor.BLUE 的值为 3 等等。)

注解

虽然我们使用 class 语法来创建 Enum,但 Enum 并不是普通的 Python 类。 更多细节请参阅 How are Enums different?

枚举成员具有适合人类阅读的表示形式:

  1. >>> print(Color.RED)
  2. Color.RED

…而它们的 repr 包含更多信息:

  1. >>> print(repr(Color.RED))
  2. <Color.RED: 1>

一个枚举成员的 type 就是它所从属的枚举:

  1. >>> type(Color.RED)
  2. <enum 'Color'>
  3. >>> isinstance(Color.GREEN, Color)
  4. True
  5. >>>

Enum 的成员还有一个包含其条目名称的特征属性:

  1. >>> print(Color.RED.name)
  2. RED

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

  1. >>> class Shake(Enum):
  2. ... VANILLA = 7
  3. ... CHOCOLATE = 4
  4. ... COOKIES = 9
  5. ... MINT = 3
  6. ...
  7. >>> for shake in Shake:
  8. ... print(shake)
  9. ...
  10. Shake.VANILLA
  11. Shake.CHOCOLATE
  12. Shake.COOKIES
  13. Shake.MINT

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

  1. >>> apples = {}
  2. >>> apples[Color.RED] = 'red delicious'
  3. >>> apples[Color.GREEN] = 'granny smith'
  4. >>> apples == {Color.RED: 'red delicious', Color.GREEN: 'granny smith'}
  5. True

对枚举成员及其属性的程序化访问

有时对枚举中的成员进行程序化访问是很有用的(例如在某些场合不能使用 Color.RED 因为在编程时并不知道要指定的确切颜色)。 Enum 允许这样的访问:

  1. >>> Color(1)
  2. <Color.RED: 1>
  3. >>> Color(3)
  4. <Color.BLUE: 3>

如果你希望通过 name 来访问枚举成员,可使用条目访问:

  1. >>> Color['RED']
  2. <Color.RED: 1>
  3. >>> Color['GREEN']
  4. <Color.GREEN: 2>

如果你有一个枚举成员并且需要它的 namevalue:

  1. >>> member = Color.RED
  2. >>> member.name
  3. 'RED'
  4. >>> member.value
  5. 1

复制枚举成员和值

不允许有同名的枚举成员:

  1. >>> class Shape(Enum):
  2. ... SQUARE = 2
  3. ... SQUARE = 3
  4. ...
  5. Traceback (most recent call last):
  6. ...
  7. TypeError: Attempted to reuse key: 'SQUARE'

但是,允许两个枚举成员有相同的值。 假定两个成员 A 和 B 有相同的值(且 A 先被定义),则 B 就是 A 的一个别名。 按值查找 A 和 B 的值将返回 A。 按名称查找 B 也将返回 A:

  1. >>> class Shape(Enum):
  2. ... SQUARE = 2
  3. ... DIAMOND = 1
  4. ... CIRCLE = 3
  5. ... ALIAS_FOR_SQUARE = 2
  6. ...
  7. >>> Shape.SQUARE
  8. <Shape.SQUARE: 2>
  9. >>> Shape.ALIAS_FOR_SQUARE
  10. <Shape.SQUARE: 2>
  11. >>> Shape(2)
  12. <Shape.SQUARE: 2>

注解

试图创建具有与某个已定义的属性(另一个成员或方法等)相同名称的成员或者试图创建具有相同名称的属性也是不允许的。

确保唯一的枚举值

默认情况下,枚举允许有多个名称作为某个相同值的别名。 如果不想要这样的行为,可以使用以下装饰器来确保每个值在枚举中只被使用一次:

  • @enum.unique
  • 专用于枚举的 class 装饰器。 它会搜索一个枚举的 members 并收集所找到的任何别名;只要找到任何别名就会引发 ValueError 并附带相关细节信息:
  1. >>> from enum import Enum, unique
  2. >>> @unique
  3. ... class Mistake(Enum):
  4. ... ONE = 1
  5. ... TWO = 2
  6. ... THREE = 3
  7. ... FOUR = 3
  8. ...
  9. Traceback (most recent call last):
  10. ...
  11. ValueError: duplicate values found in <enum 'Mistake'>: FOUR -> THREE

使用自动设定的值

如果确切的值不重要,你可以使用 auto:

  1. >>> from enum import Enum, auto
  2. >>> class Color(Enum):
  3. ... RED = auto()
  4. ... BLUE = auto()
  5. ... GREEN = auto()
  6. ...
  7. >>> list(Color)
  8. [<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

值将由 generate_next_value() 来选择,该函数可以被重载:

  1. >>> class AutoName(Enum):
  2. ... def _generate_next_value_(name, start, count, last_values):
  3. ... return name
  4. ...
  5. >>> class Ordinal(AutoName):
  6. ... NORTH = auto()
  7. ... SOUTH = auto()
  8. ... EAST = auto()
  9. ... WEST = auto()
  10. ...
  11. >>> list(Ordinal)
  12. [<Ordinal.NORTH: 'NORTH'>, <Ordinal.SOUTH: 'SOUTH'>, <Ordinal.EAST: 'EAST'>, <Ordinal.WEST: 'WEST'>]

注解

默认 generate_next_value() 方法的目标是提供所给出的最后一个 int 所在序列的下一个 int,但这种行为方式属于实现细节并且可能发生改变。

迭代

对枚举成员的迭代不会给出别名:

  1. >>> list(Shape)
  2. [<Shape.SQUARE: 2>, <Shape.DIAMOND: 1>, <Shape.CIRCLE: 3>]

特殊属性 members 是一个从名称到成员的只读有序映射。 它包含枚举中定义的所有名称,包括别名:

  1. >>> for name, member in Shape.__members__.items():
  2. ... name, member
  3. ...
  4. ('SQUARE', <Shape.SQUARE: 2>)
  5. ('DIAMOND', <Shape.DIAMOND: 1>)
  6. ('CIRCLE', <Shape.CIRCLE: 3>)
  7. ('ALIAS_FOR_SQUARE', <Shape.SQUARE: 2>)

members 属性可被用于对枚举成员进行详细的程序化访问。 例如,找出所有别名:

  1. >>> [name for name, member in Shape.__members__.items() if member.name != name]
  2. ['ALIAS_FOR_SQUARE']

比较

枚举成员是按标识号进行比较的:

  1. >>> Color.RED is Color.RED
  2. True
  3. >>> Color.RED is Color.BLUE
  4. False
  5. >>> Color.RED is not Color.BLUE
  6. True

枚举值之间的排序比较 不被 支持。 Enum 成员不属于整数 (另请参阅下文的 IntEnum):

  1. >>> Color.RED < Color.BLUE
  2. Traceback (most recent call last):
  3. File "<stdin>", line 1, in <module>
  4. TypeError: '<' not supported between instances of 'Color' and 'Color'

相等比较的定义如下:

  1. >>> Color.BLUE == Color.RED
  2. False
  3. >>> Color.BLUE != Color.RED
  4. True
  5. >>> Color.BLUE == Color.BLUE
  6. True

与非枚举值的比较将总是不相等(同样地,IntEnum 被显式设计成不同的行为,参见下文):

  1. >>> Color.BLUE == 2
  2. False

允许的枚举成员和属性

以上示例使用整数作为枚举值。 使用整数相当简洁方便(并由 Functional API 默认提供),但并不强制要求使用。 在大部分用例中,开发者都关心枚举的实际值是什么。 但如果值 确实 重要,则枚举可以使用任意的值。

枚举属于 Python 的类,并可具有普通方法和特殊方法。 如果我们有这样一个枚举:

  1. >>> class Mood(Enum):
  2. ... FUNKY = 1
  3. ... HAPPY = 3
  4. ...
  5. ... def describe(self):
  6. ... # self is the member here
  7. ... return self.name, self.value
  8. ...
  9. ... def __str__(self):
  10. ... return 'my custom str! {0}'.format(self.value)
  11. ...
  12. ... @classmethod
  13. ... def favorite_mood(cls):
  14. ... # cls here is the enumeration
  15. ... return cls.HAPPY
  16. ...

那么:

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

对于允许内容的规则如下:以单下划线开头和结尾的名称是由枚举保留而不可使用;在枚举中定义的所有其他属性将成为该枚举的成员,例外项则包括特殊方法成员 (str(), add() 等),描述符 (方法也属于描述符) 以及在 ignore 中列出的变量名。

注意:如果你的枚举定义了 new() 和/或 init() 那么指定给枚举成员的任何值都会被传入这些方法。 请参阅示例 Planet

受限的 Enum 子类化

一个新的 Enum 类必须基于一个 Enum 类,至多一个实体数据类型以及出于实际需要的任意多个基于 object 的 mixin 类。 这些基类的顺序为:

  1. class EnumName([mix-in, ...,] [data-type,] base-enum):
  2. pass

另外,仅当一个枚举未定义任何成员时才允许子类化该枚举。 因此禁止这样的写法:

  1. >>> class MoreColor(Color):
  2. ... PINK = 17
  3. ...
  4. Traceback (most recent call last):
  5. ...
  6. TypeError: Cannot extend enumerations

但是允许这样的写法:

  1. >>> class Foo(Enum):
  2. ... def some_behavior(self):
  3. ... pass
  4. ...
  5. >>> class Bar(Foo):
  6. ... HAPPY = 1
  7. ... SAD = 2
  8. ...

允许子类化定义了成员的枚举将会导致违反类型与实例的某些重要的不可变规则。 在另一方面,允许在一组枚举之间共享某些通用行为也是有意义的。 (请参阅示例 OrderedEnum 。)

封存

枚举可以被封存与解封:

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

封存的常规限制同样适用:可封存枚举必须在模块的最高层级中定义,因为解封操作要求它们可以从该模块导入。

注解

使用 pickle 协议版本 4 可以方便地封存嵌套在其他类中的枚举。

通过在枚举类中定义 reduce_ex() 可以对 Enum 成员的封存/解封方式进行修改。

功能性 API

Enum 类属于可调用对象,它提供了以下功能性 API:

  1. >>> Animal = Enum('Animal', 'ANT BEE CAT DOG')
  2. >>> Animal
  3. <enum 'Animal'>
  4. >>> Animal.ANT
  5. <Animal.ANT: 1>
  6. >>> Animal.ANT.value
  7. 1
  8. >>> list(Animal)
  9. [<Animal.ANT: 1>, <Animal.BEE: 2>, <Animal.CAT: 3>, <Animal.DOG: 4>]

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

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

  1. >>> class Animal(Enum):
  2. ... ANT = 1
  3. ... BEE = 2
  4. ... CAT = 3
  5. ... DOG = 4
  6. ...

默认以 1 而以 0 作为起始数值的原因在于 0 的布尔值为 False,但所有枚举成员都应被求值为 True

对使用功能性 API 创建的枚举执行封存可能会很麻烦,因为要使用帧堆栈的实现细节来尝试并找出枚举是在哪个模块中创建的(例如当你使用了另一个模块中的工具函数就可能失败,在 IronPython 或 Jython 上也可能无效)。 解决办法是显式地指定模块名称,如下所示:

  1. >>> Animal = Enum('Animal', 'ANT BEE CAT DOG', module=__name__)

警告

如果未提供 module,且 Enum 无法确定是哪个模块,新的 Enum 成员将不可被解封;为了让错误尽量靠近源头,封存将被禁用。

新的 pickle 协议版本 4 在某些情况下同样依赖于 qualname 被设为特定位置以便 pickle 能够找到相应的类。 例如,类是否存在于全局作用域的 SomeData 类中:

  1. >>> Animal = Enum('Animal', 'ANT BEE CAT DOG', qualname='SomeData.Animal')

完整的签名为:

  1. Enum(value='NewEnumName', names=<...>, *, module='...', qualname='...', type=<mixed-in class>, start=1)
  • 将被新 Enum 类将记录为其名称的数据。

  • 名称

  • Enum 的成员。 这可以是一个空格或逗号分隔的字符串 (起始值将为 1,除非另行指定):
  1. 'RED GREEN BLUE' | 'RED,GREEN,BLUE' | 'RED, GREEN, BLUE'

或是一个名称的迭代器:

  1. ['RED', 'GREEN', 'BLUE']

或是一个 (名称, 值) 对的迭代器:

  1. [('CYAN', 4), ('MAGENTA', 5), ('YELLOW', 6)]

或是一个映射:

  1. {'CHARTREUSE': 7, 'SEA_GREEN': 11, 'ROSEMARY': 42}
  • 模块
  • 新 Enum 类所在模块的名称。

  • qualname

  • 新 Enum 类在模块中的具体位置。

  • 类型

  • 要加入新 Enum 类的类型。

  • start

  • 当只传入名称时要使用的起始数值。

在 3.5 版更改: 增加了 start 形参。

派生的枚举

IntEnum

所提供的第一个变种 Enum 同时也是 int 的一个子类。 IntEnum 的成员可与整数进行比较;通过扩展,不同类型的整数枚举也可以相互进行比较:

  1. >>> from enum import IntEnum
  2. >>> class Shape(IntEnum):
  3. ... CIRCLE = 1
  4. ... SQUARE = 2
  5. ...
  6. >>> class Request(IntEnum):
  7. ... POST = 1
  8. ... GET = 2
  9. ...
  10. >>> Shape == 1
  11. False
  12. >>> Shape.CIRCLE == 1
  13. True
  14. >>> Shape.CIRCLE == Request.POST
  15. True

不过,它们仍然不可与标准 Enum 枚举进行比较:

  1. >>> class Shape(IntEnum):
  2. ... CIRCLE = 1
  3. ... SQUARE = 2
  4. ...
  5. >>> class Color(Enum):
  6. ... RED = 1
  7. ... GREEN = 2
  8. ...
  9. >>> Shape.CIRCLE == Color.RED
  10. False

IntEnum 值在其他方面的行为都如你预期的一样类似于整数:

  1. >>> int(Shape.CIRCLE)
  2. 1
  3. >>> ['a', 'b', 'c'][Shape.CIRCLE]
  4. 'b'
  5. >>> [i for i in range(Shape.SQUARE)]
  6. [0, 1]

IntFlag

所提供的下一个 Enum 的变种 IntFlag 同样是基于 int 的,不同之处在于 IntFlag 成员可使用按位运算符 (&, |, ^, ~) 进行组合且结果仍然为 IntFlag 成员。 如果,正如名称所表明的,IntFlag 成员同时也是 int 的子类,并能在任何使用 int 的场合被使用。 IntFlag 成员进行除按位运算以外的其他运算都将导致失去 IntFlag 成员资格。

3.6 新版功能.

示例 IntFlag 类:

  1. >>> from enum import IntFlag
  2. >>> class Perm(IntFlag):
  3. ... R = 4
  4. ... W = 2
  5. ... X = 1
  6. ...
  7. >>> Perm.R | Perm.W
  8. <Perm.R|W: 6>
  9. >>> Perm.R + Perm.W
  10. 6
  11. >>> RW = Perm.R | Perm.W
  12. >>> Perm.R in RW
  13. True

对于组合同样可以进行命名:

  1. >>> class Perm(IntFlag):
  2. ... R = 4
  3. ... W = 2
  4. ... X = 1
  5. ... RWX = 7
  6. >>> Perm.RWX
  7. <Perm.RWX: 7>
  8. >>> ~Perm.RWX
  9. <Perm.-8: -8>

IntFlagEnum 的另一个重要区别在于如果没有设置任何旗标(值为 0),则其布尔值为 False:

  1. >>> Perm.R & Perm.X
  2. <Perm.0: 0>
  3. >>> bool(Perm.R & Perm.X)
  4. False

由于 IntFlag 成员同时也是 int 的子类,因此它们可以相互组合:

  1. >>> Perm.X | 8
  2. <Perm.8|X: 9>

Flag

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

3.6 新版功能.

IntFlag 类似,如果 Flag 成员的某种组合导致没有设置任何旗标,则其布尔值为 False:

  1. >>> from enum import Flag, auto
  2. >>> class Color(Flag):
  3. ... RED = auto()
  4. ... BLUE = auto()
  5. ... GREEN = auto()
  6. ...
  7. >>> Color.RED & Color.GREEN
  8. <Color.0: 0>
  9. >>> bool(Color.RED & Color.GREEN)
  10. False

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

  1. >>> class Color(Flag):
  2. ... RED = auto()
  3. ... BLUE = auto()
  4. ... GREEN = auto()
  5. ... WHITE = RED | BLUE | GREEN
  6. ...
  7. >>> Color.WHITE
  8. <Color.WHITE: 7>

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

  1. >>> class Color(Flag):
  2. ... BLACK = 0
  3. ... RED = auto()
  4. ... BLUE = auto()
  5. ... GREEN = auto()
  6. ...
  7. >>> Color.BLACK
  8. <Color.BLACK: 0>
  9. >>> bool(Color.BLACK)
  10. False

注解

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

其他事项

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

  1. class IntEnum(int, Enum):
  2. pass

这里演示了如何定义类似的派生枚举;例如一个混合了 str 而不是 intStrEnum

几条规则:

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

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

  • 当混合了另一数据类型时,value 属性会 不同于 枚举成员自身,但它们仍保持等价且比较结果也相等。

  • %-style formatting: %s%r 会分别调用 Enum 类的 str()repr();其他代码 (例如表示 IntEnum 的 %i%h) 会将枚举成员视为对应的混合类型。

  • 格式化字符串字面值, str.format()format() 将使用混合类型的 format()。 如果需要 Enum 类的 str()repr(),请使用 !s!r 格式代码。

何时使用 new() 与 init()

当你想要定制 Enum 成员的实际值时必须使用 new()。 任何其他修改可以用 new() 也可以用 init(),应优先使用 init()

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

  1. >>> class Coordinate(bytes, Enum):
  2. ... """
  3. ... Coordinate with binary codes that can be indexed by the int code.
  4. ... """
  5. ... def __new__(cls, value, label, unit):
  6. ... obj = bytes.__new__(cls, [value])
  7. ... obj._value_ = value
  8. ... obj.label = label
  9. ... obj.unit = unit
  10. ... return obj
  11. ... PX = (0, 'P.X', 'km')
  12. ... PY = (1, 'P.Y', 'km')
  13. ... VX = (2, 'V.X', 'km/s')
  14. ... VY = (3, 'V.Y', 'km/s')
  15. ...
  16.  
  17. >>> print(Coordinate['PY'])
  18. Coordinate.PY
  19.  
  20. >>> print(Coordinate(3))
  21. Coordinate.VY

有趣的示例

虽然 Enum, IntEnum, IntFlagFlag 预期可覆盖大多数应用场景,但它们无法覆盖全部。 这里有一些不同类型枚举的方案,它们可以被直接使用,或是作为自行创建的参考示例。

省略值

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

  • 使用 auto 的实例作为值

  • 使用 object 的实例作为值

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

  • 使用元组作为值并用自定义的 new() 以一个 int 值来替代该元组

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

无论你选择何种方法,你都应当提供一个 repr() 并且它也需要隐藏(不重要的)值:

  1. >>> class NoValue(Enum):
  2. ... def __repr__(self):
  3. ... return '<%s.%s>' % (self.__class__.__name__, self.name)
  4. ...

使用 auto

使用 auto 的形式如下:

  1. >>> class Color(NoValue):
  2. ... RED = auto()
  3. ... BLUE = auto()
  4. ... GREEN = auto()
  5. ...
  6. >>> Color.GREEN
  7. <Color.GREEN>

使用 object

使用 object 的形式如下:

  1. >>> class Color(NoValue):
  2. ... RED = object()
  3. ... GREEN = object()
  4. ... BLUE = object()
  5. ...
  6. >>> Color.GREEN
  7. <Color.GREEN>

使用描述性字符串

使用字符串作为值的形式如下:

  1. >>> class Color(NoValue):
  2. ... RED = 'stop'
  3. ... GREEN = 'go'
  4. ... BLUE = 'too fast!'
  5. ...
  6. >>> Color.GREEN
  7. <Color.GREEN>
  8. >>> Color.GREEN.value
  9. 'go'

使用自定义的 new()

使用自动编号 new() 的形式如下:

  1. >>> class AutoNumber(NoValue):
  2. ... def __new__(cls):
  3. ... value = len(cls.__members__) + 1
  4. ... obj = object.__new__(cls)
  5. ... obj._value_ = value
  6. ... return obj
  7. ...
  8. >>> class Color(AutoNumber):
  9. ... RED = ()
  10. ... GREEN = ()
  11. ... BLUE = ()
  12. ...
  13. >>> Color.GREEN
  14. <Color.GREEN>
  15. >>> Color.GREEN.value
  16. 2

注解

如果定义了 new() 则它会在创建 Enum 成员期间被使用;随后它将被 Enum 的 new() 所替换,该方法会在类创建后被用来查找现有成员。

OrderedEnum

一个有序枚举,它不是基于 IntEnum,因此保持了正常的 Enum 不变特性(例如不可与其他枚举进行比较):

  1. >>> class OrderedEnum(Enum):
  2. ... def __ge__(self, other):
  3. ... if self.__class__ is other.__class__:
  4. ... return self.value >= other.value
  5. ... return NotImplemented
  6. ... def __gt__(self, other):
  7. ... if self.__class__ is other.__class__:
  8. ... return self.value > other.value
  9. ... return NotImplemented
  10. ... def __le__(self, other):
  11. ... if self.__class__ is other.__class__:
  12. ... return self.value <= other.value
  13. ... return NotImplemented
  14. ... def __lt__(self, other):
  15. ... if self.__class__ is other.__class__:
  16. ... return self.value < other.value
  17. ... return NotImplemented
  18. ...
  19. >>> class Grade(OrderedEnum):
  20. ... A = 5
  21. ... B = 4
  22. ... C = 3
  23. ... D = 2
  24. ... F = 1
  25. ...
  26. >>> Grade.C < Grade.A
  27. True

DuplicateFreeEnum

如果发现重复的成员名称则将引发错误而不是创建别名:

  1. >>> class DuplicateFreeEnum(Enum):
  2. ... def __init__(self, *args):
  3. ... cls = self.__class__
  4. ... if any(self.value == e.value for e in cls):
  5. ... a = self.name
  6. ... e = cls(self.value).name
  7. ... raise ValueError(
  8. ... "aliases not allowed in DuplicateFreeEnum: %r --> %r"
  9. ... % (a, e))
  10. ...
  11. >>> class Color(DuplicateFreeEnum):
  12. ... RED = 1
  13. ... GREEN = 2
  14. ... BLUE = 3
  15. ... GRENE = 2
  16. ...
  17. Traceback (most recent call last):
  18. ...
  19. ValueError: aliases not allowed in DuplicateFreeEnum: 'GRENE' --> 'GREEN'

注解

这个例子适用于子类化 Enum 来添加或改变禁用别名以及其他行为。 如果需要的改变只是禁用别名,也可以选择使用 unique() 装饰器。

Planet

如果定义了 new()init() 则枚举成员的值将被传给这些方法:

  1. >>> class Planet(Enum):
  2. ... MERCURY = (3.303e+23, 2.4397e6)
  3. ... VENUS = (4.869e+24, 6.0518e6)
  4. ... EARTH = (5.976e+24, 6.37814e6)
  5. ... MARS = (6.421e+23, 3.3972e6)
  6. ... JUPITER = (1.9e+27, 7.1492e7)
  7. ... SATURN = (5.688e+26, 6.0268e7)
  8. ... URANUS = (8.686e+25, 2.5559e7)
  9. ... NEPTUNE = (1.024e+26, 2.4746e7)
  10. ... def __init__(self, mass, radius):
  11. ... self.mass = mass # in kilograms
  12. ... self.radius = radius # in meters
  13. ... @property
  14. ... def surface_gravity(self):
  15. ... # universal gravitational constant (m3 kg-1 s-2)
  16. ... G = 6.67300E-11
  17. ... return G * self.mass / (self.radius * self.radius)
  18. ...
  19. >>> Planet.EARTH.value
  20. (5.976e+24, 6378140.0)
  21. >>> Planet.EARTH.surface_gravity
  22. 9.802652743337129

TimePeriod

一个演示如何使用 ignore 属性的例子:

  1. >>> from datetime import timedelta
  2. >>> class Period(timedelta, Enum):
  3. ... "different lengths of time"
  4. ... _ignore_ = 'Period i'
  5. ... Period = vars()
  6. ... for i in range(367):
  7. ... Period['day_%d' % i] = i
  8. ...
  9. >>> list(Period)[:2]
  10. [<Period.day_0: datetime.timedelta(0)>, <Period.day_1: datetime.timedelta(days=1)>]
  11. >>> list(Period)[-2:]
  12. [<Period.day_365: datetime.timedelta(days=365)>, <Period.day_366: datetime.timedelta(days=366)>]

各种枚举有何区别?

枚举具有自定义的元类,它会影响所派生枚举类及其实例(成员)的各个方面。

枚举类

EnumMeta 元类负责提供 contains(), dir(), iter() 及其他方法以允许用户通过 Enum 类来完成一般类做不到的事情,例如 list(Color)some_enum_var in ColorEnumMeta 会负责确保最终 Enum 类中的各种其他方法是正确的 (例如 new(), getnewargs(), str()repr())。

枚举成员(即实例)

有关枚举成员最有趣的特点是它们都是单例对象。 EnumMeta 会在创建 Enum 类本身时将它们全部创建完成,然后准备好一个自定义的 new(),通过只返回现有的成员实例来确保不会再实例化新的对象。

细节要点

支持的 dunder 名称

members 是一个 member_name:member 条目的只读有序映射。 它只在类上可用。

如果指定了 new(),它必须创建并返回枚举成员;相应地设定成员的 value 也是一个很好的主意。 一旦所有成员都创建完成它就不会再被使用。

支持的 sunder 名称

  • name — 成员的名称

  • value — 成员的值;可以在 new 中设置 / 修改

  • missing — 当未发现某个值时所使用的查找函数;可被重载

  • ignore — 一个名称列表,可以为 list()str(),它将不会被转化为成员,并会从最终类中被移除

  • order — 用于 Python 2/3 代码以确保成员顺序一致(类属性,在类创建期间会被移除)

  • generate_next_value — 用于 Functional API 并通过 auto 为枚举成员获取适当的值;可被重载

3.6 新版功能: missing, order, generate_next_value

3.7 新版功能: ignore

用来帮助 Python 2 / Python 3 代码保持同步提供 order 属性。 它将与枚举的实际顺序进行对照检查,如果两者不匹配则会引发错误:

  1. >>> class Color(Enum):
  2. ... _order_ = 'RED GREEN BLUE'
  3. ... RED = 1
  4. ... BLUE = 3
  5. ... GREEN = 2
  6. ...
  7. Traceback (most recent call last):
  8. ...
  9. TypeError: member order does not match _order_

注解

在 Python 2 代码中 order 属性是必须的,因为定义顺序在被记录之前就会丢失。

Enum 成员类型

Enum 成员是其 Enum 类的实例,一般通过 EnumClass.member 的形式来访问。 在特定情况下它们也可通过 EnumClass.member.member 的形式来访问,但你绝对不应这样做,因为查找可能失败,或者更糟糕地返回你所查找的 Enum 成员以外的对象(这也是成员应使用全大写名称的另一个好理由):

  1. >>> class FieldTypes(Enum):
  2. ... name = 0
  3. ... value = 1
  4. ... size = 2
  5. ...
  6. >>> FieldTypes.value.size
  7. <FieldTypes.size: 2>
  8. >>> FieldTypes.size.value
  9. 2

在 3.5 版更改.

Enum 类和成员的布尔值

混合了非 Enum 类型(例如 int, str 等)的 Enum 成员会按所混合类型的规则被求值;在其他情况下,所有成员都将被求值为 True。 要使你的自定义 Enum 的布尔值取决于成员的值,请在你的类中添加以下代码:

  1. def __bool__(self):
  2. return bool(self.value)

Enum 类总是会被求值为 True

带有方法的 Enum 类

如果你为你的 Enum 子类添加了额外的方法,如同上述的 Planet 类一样,这些方法将在对成员执行 dir() 时显示出来,但对类执行时则不会显示:

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

组合 Flag 的成员

如果 Flag 成员的某种组合未被命名,则 repr() 将包含所有已命名的旗标和值中所有已命名的旗标组合:

  1. >>> class Color(Flag):
  2. ... RED = auto()
  3. ... GREEN = auto()
  4. ... BLUE = auto()
  5. ... MAGENTA = RED | BLUE
  6. ... YELLOW = RED | GREEN
  7. ... CYAN = GREEN | BLUE
  8. ...
  9. >>> Color(3) # named combination
  10. <Color.YELLOW: 3>
  11. >>> Color(7) # not named combination
  12. <Color.CYAN|MAGENTA|BLUE|YELLOW|GREEN|RED: 7>