Python,Enum类型的优点是什么?

Aar*_*all 35 python enums

在Python 3.4中,我们在标准库中获得了一个Enum lib : enum. 我们可以enum使用Python 2.4到2.7(甚至3.1到3.3),pypi中的enum34来获得一个后端.

但是,如果没有这个新模块,我们已经相处了很长一段时间 - 那么为什么我们现在拥有它呢?

我对其他语言的枚举目的有一个大概的了解.在Python中,通常使用如下的裸类并将其称为枚举:

class Colors:
    blue = 1
    green = 2
    red = 3
Run Code Online (Sandbox Code Playgroud)

这可以在API中用于创建值的规范表示,例如:

function_of_color(Colors.green)
Run Code Online (Sandbox Code Playgroud)

如果这有任何批评,它是可变的,你不能迭代它(很容易),我们如何知道整数的语义2

那么我想我可以使用像namedtuple这样的东西,它是不可变的?

>>> 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
Run Code Online (Sandbox Code Playgroud)

namedtuple的创建有点多余(我们必须将每个名称写两次),因此有点不优雅.获得颜色的"数字"也有点不优雅(我们必须写colors两次).必须使用字符串进行值检查,效率稍低.

所以回到枚举.

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

Aar*_*all 67

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

Enum类型通过PEP 435进入Python .给出的推理是:

枚举的属性对于定义可能具有或不具有语义含义的不可变的相关常量值集非常有用.

当为此目的使用数字和字符串时,它们可以被表征为"魔术数字"或"魔术字符串".数字很​​少带有语义,字符串容易混淆(大写?拼写?蛇或骆驼案?)

一周中的几天和学校字母等级是这种价值集合的例子.

以下是文档中的示例:

from enum import Enum

class Color(Enum):
    red = 1
    green = 2
    blue = 3
Run Code Online (Sandbox Code Playgroud)

就像裸类一样,它比namedtuple示例更具可读性和优雅性,它也是不可变的,并且它还有其他好处,我们将在下面看到.

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

>>> type(Color.red)
<enum 'Color'>
>>> isinstance(Color.green, Color)
True
Run Code Online (Sandbox Code Playgroud)

这允许您在Enum定义中定义成员的功能.定义值的功能可以使用其他现有方法来完成,但它将非常不优雅.

改进:字符串强制

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

>>> print(Color.red)
Color.red
>>> print(repr(Color.red))
<Color.red: 1>
Run Code Online (Sandbox Code Playgroud)

我发现这是对魔术数字的改进,甚至可能比来自namedtuple的字符串更好.

迭代(奇偶校验):

枚举支持迭代(比如namedtuple,但不是那么裸的类):

>>> for color in Color:
        print(color)
Color.red
Color.green
Color.blue
Run Code Online (Sandbox Code Playgroud)

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

由泡菜(奇偶校验)支持

您可以序列化和反序列化枚举(如果有人担心这个):

>>> import pickle
>>> color.red is pickle.loads(pickle.dumps(color.red))
True
Run Code Online (Sandbox Code Playgroud)

改进:别名

这是一个很好的功能,裸类没有,并且很难说别名在那里namedtuple.

class Color(Enum):
    red = 1
    green = 2
    blue = 3
    really_blue = 3
Run Code Online (Sandbox Code Playgroud)

别名来自规范名称,但它们都是相同的:

>>> Color.blue is Color.really_blue
True
Run Code Online (Sandbox Code Playgroud)

如果应禁止别名以避免值冲突,请使用enum.unique装饰器(严格占优势的特征).

严格占优势:与之相比较 is

枚举旨在进行测试is,这是在过程中快速检查单个对象的身份.

>>> Color.red is Color.red
True
>>> Color.red is Color.blue
False
>>> Color.red is not Color.blue
True
Run Code Online (Sandbox Code Playgroud)

对平等的测试也是如此,但对身份的测试is是最佳的.

与其他Python类不同的语义

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

>>> Color.red is Color('red')
Run Code Online (Sandbox Code Playgroud)

记住这一点很重要,也许这是一个缺点,但在这个方面进行比较是将苹果与橙子进行比较.

不假定订购的枚举

虽然Enum类知道创建成员的顺序,但不假定枚举是有序的.这是一个特征,因为许多可能枚举的东西没有自然顺序,因此顺序是任意的.

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

子类

您不能使用已声明的成员对Enum 进行子类化,但您可以将未声明成员的Enum子类化为共享行为(请参阅文档中的OrderedEnum配方).

这是一个特性 - 将Enum与成员子类化是没有意义的,但同样,比较是苹果和橙子.

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

这是Python中新的规范枚举.合作者希望您的枚举行为与这些枚举一样.

如果您希望明确指定使用规范名称而不是任意数据,则在代码中具有规范数据源的任何位置使用它.

例如,如果在你的代码,你希望用户指出它不是"Green","green",2,或"Greene",但Color.green-使用enum.Enum对象.它既明确又具体.

文档中有很多示例和配方.

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

停止自己滚动或让人们猜测魔术数字和字符串.不要避免它们.拥抱他们.

但是,如果由于历史原因要求枚举成员是整数,则IntEnum来自同一模块,它具有相同的行为,但也是一个整数,因为它int在子类化之前将内置子类化Enum.来自IntEnum他的帮助:

class IntEnum(builtins.int, Enum)
Run Code Online (Sandbox Code Playgroud)

我们可以看到IntEnum值将作为一个实例进行测试int.

  • 非常感谢这个全面的答案。在我看来,现在可以用文字类型替换(完全?)枚举 https://www.python.org/dev/peps/pep-0586/ -对此有何想法? (4认同)
  • 找不到专门针对 python 的“枚举与文字类型”的良好讨论,但这篇文章是针对 TypeScript 的,有些人可能仍然觉得有用:/sf/ask/3483338071/ Between-string- ts 中的枚举和字符串文字类型 (3认同)
  • 我想我可能会收回该声明中更全面(即“完全”)的部分:)但是作为这种替换的一个例子,我们使用了很多 `class SomeCollection(str, Enum)` 类,直到我们找到了文字。替换这些之后,我们 a) 认为它更容易阅读(字符串现在看起来像字符串,而不是类的属性),b) 我们可以将它们组合起来,例如 `LargerCollection = Literal[SmallCollection1, SmallCollection2]`,我们无法使用枚举(这对我们来说是最相关的原因)。非常感谢这是一个特殊情况,而不是一般情况。 (2认同)