Python, what's the Enum type good for?

In Python 3.4, we got an Enum lib in the standard library: enum. We can get a backport for enum that works with Python 2.4 to 2.7 (and even 3.1 to 3.3), enum34 in pypi.

But we've managed to get along for quite some time without this new module - so why do we now have it?

I have a general idea about the purpose of enums from other languages. In Python, it has been common to use a bare class as follows and refer to this as an enum:

class Colors:
blue = 1
green = 2
red = 3

This can be used in an API to create a canonical representation of the value, e.g.:

function_of_color(Colors.green)

If this has any criticisms, it's mutable, you can't iterate over it (easily), and how are we to know the semantics of the integer, 2?

Then I suppose I could just use something like a namedtuple, which would be immutable?

>>> Colors = namedtuple('Colors', 'blue green red')
>>> colors = Colors('blue', 'green', 'red')
>>> colors
Colors(blue='blue', green='green', red='red')
>>> list(colors)
['blue', 'green', 'red']
>>> len(colors)
3
>>> colors.blue
'blue'
>>> colors.index(colors.blue)
0

The creation of the namedtuple is a little redundant (we have to write each name twice), and so somewhat inelegant. Getting the "number" of the color is also a little inelegant (we have to write colors twice). Value checking will have to be done with strings, which will be a little less efficient.

So back to enums.

What's the purpose of enums? What value do they create for the language? When should I use them and when should I avoid them?

70027 次浏览

枚举的目的是什么?它们为语言创造了什么价值?我什么时候应该使用它们,什么时候应该避免使用它们?

Enum 类型通过 PEP 435进入 Python:

枚举的属性对于定义一组不可变的、相关的、可能具有或不具有语义意义的常量值非常有用。

当为此目的使用数字和字符串时,它们可以被描述为 “神奇数字”或“魔术字符串”。数字很少带有语义,字符串很容易混淆(大写?拼写?蛇皮还是骆驼皮?)

每周的日子和学校的信件等级就是这种价值观集合的例子。

下面是 医生的一个例子:

from enum import Enum


class Color(Enum):
red = 1
green = 2
blue = 3

与裸类一样,这比 namedtuple 示例更具可读性和优雅性,它也是不可变的,而且它还有更多的好处,我们将在下面看到。

严格占优势: 枚举成员的类型是枚举

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

这允许您在 Enum 定义中定义成员的功能。在值上定义功能可以使用其他先前的方法来完成,但是这样做非常不雅观。

改进: 字符串强制

字符串表示是人类可读的,而代表有更多的信息:

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

我发现这是对神奇数字的一个改进,甚至可能比命名元组中的字符串更好。

迭代(奇偶校验) :

枚举也支持迭代(类似于 namedtuple,但不太支持裸类) :

>>> for color in Color:
print(color)
Color.red
Color.green
Color.blue

__members__属性是枚举名称到它们各自的枚举对象(类似于 namedtuple 的 _asdict()函数)的有序映射。

>>> Color.__members__
mappingproxy(OrderedDict([('red', <Color.red: 1>), ('green', <Color.green: 2>),
('blue', <Color.blue: 3>)]))

由 pickle (奇偶校验)支持

您可以序列化和反序列化枚举(以防有人担心这个问题) :

>>> import pickle
>>> color.red is pickle.loads(pickle.dumps(color.red))
True

改进: 别名

这是一个很好的特性,裸类没有这个特性,而且很难说别名在 namedtuple中是否存在。

class Color(Enum):
red = 1
green = 2
blue = 3
really_blue = 3

别名在规范名称之后,但它们都是相同的:

>>> Color.blue is Color.really_blue
True

如果为了避免值冲突而禁止使用别名,那么可以使用 enum.unique修饰符(这是一个非常重要的特性)。

严格占优势: 与 is的比较

The enum is intended to be tested with is, which is a fast check for a single object's identity in the process.

>>> Color.red is Color.red
True
>>> Color.red is Color.blue
False
>>> Color.red is not Color.blue
True

相等性测试也可以工作,但是与 is的一致性测试是最佳的。

与其他 Python 类不同的语义

Enum 类具有与常规 Python 类型不同的语义。Enum 的值是 Enum 的实例,并且是这些值的内存中的单例——实例化它们没有其他目的。

>>> Color.red is Color(1)

This is important to keep in mind, perhaps it is a downside, but comparing on this dimension is comparing apples with oranges.

不假定排序的枚举

虽然 Enum 类知道创建成员的顺序,但不假定枚举是按顺序创建的。这是一个特性,因为可以枚举的许多事物没有自然顺序,因此顺序是任意的。

但是,您可以给出枚举顺序(请参阅下一节)。

Subclassing

You can't subclass an Enum with members declared, but you 可以 subclass an Enum that doesn't declare members to share behavior (see the OrderedEnum recipe in the 医生).

This is a feature - it makes little sense to subclass an Enum with members, but again, the comparison is apples and oranges.

我应该什么时候使用 enum.Enum

这是 Python 中新的规范枚举。

在代码中有枚举数据的规范源的任何地方使用它,您可以在其中显式指定使用规范名称,而不是使用任意数据。

例如,如果您希望用户在代码中声明它不是 "Green""green"、2或 "Greene",而是 Color.green-使用枚举。Enum 对象。既明确又具体。

文件中有很多例子和食谱。

我什么时候应该避开他们?

Stop rolling your own or letting people guess about magic numbers and strings. Don't avoid them. Embrace them.

但是,如果由于历史原因,枚举成员必须是整数,那么同一个模块中的 IntEnum具有相同的行为,但也是一个整数,因为它在子类化 Enum之前子类化了内置的 int。在 IntEnum的帮助下:

class IntEnum(builtins.int, Enum)

我们可以看到,IntEnum 值将作为 int的实例进行测试。