enum 和 namedtuple 有什么区别?

What's the difference between enum and namedtuple?

我想知道 enum 和 namedtuple 之间的区别是什么,以及什么时候应该使用一个而不是另一个。

作为类比(尽管不完美),您可以将 python 中的 enum.Enumnamedtuple 视为 C 中的 enumstruct换句话说,enum是一种取别名的方式,而namedtuple是一种通过名称封装数据的方式。两者实际上不能互换,您可以使用 enums 作为 namedtuple.

中的命名值

我认为这个例子说明了区别。

from collections import namedtuple
from enum import Enum

class HairColor(Enum):
    blonde = 1
    brown = 2
    black = 3
    red = 4

Person = namedtuple('Person', ['name','age','hair_color'])
bert = Person('Bert', 5, HairColor.black)

您可以像访问常规对象一样访问此人的命名 "attributes"。

>>> print(bert.name)
Bert
>>> print(bert.age)
5
>>> print(bert.hair_color)
HairColor.black
>>> print(bert.hair_color.value)
3

您通常看不到这样的 namedtuple,因为可以通过使用更广为人知的 class 声明来实现相同的基本概念。下面的 class 定义与上面的 namedtuple 定义几乎完全相同。

class Person:
    def __init__(self, name, age, hair_color):
        self.name = name
        self.age = age
        self.hair_color = hair_color

但是,namedtupleclass 对象之间的主要区别在于 namedtuple 的属性在创建后无法更改。

namedtuple 是一个 fast 结构,使用 __slots__ 代替__dict__,最终确定您在初始化时提供的内容(尽管存在 _replace() 方法,但实际上变为只读)。

当您需要许多(例如数百、数千甚至数百万)相同类型的对象或者您正在读取 and/or 写入记录时,通常会使用 namedtuple。
例如,一个经常被引用的例子是一个 Point namedtuple,它可以用于处理具有 x, y, z 个组件的多边形顶点。
如果与始终按名称指向正确组件的好处相比,namedtuple 在常规元组上引入的开销是最小的 (.x, .y, .z, ...) 而不是按索引 (0, 1, 2, ...)。
阅读 A.x 之类的代码比 A[0] 更容易:即使在您编写代码几个月后,其含义也很明显,更好的是,对于其他程序员也是如此。

因此 namedtuple 速度很快,可用于有意义地标识元组的内容,最后但并非最不重要的一点是,可以与通过索引访问元组内容的旧代码共存。

from collections import namedtuple

Point = namedtuple('Point', 'x y z')  # note the x, y, z fields

origin = Point(0, 0, 0)

A = Point(1, 1, 1)
B = Point(1, 1, 0)
C = Point(1, 0, 0)
D = Point(1, 2, 3)

for p in (origin, A, B, C, D):
    print(p)
    print('x:', p.x, '  y:', p.y, '  z:', p.z)
    print('x:', p[0], '  y:', p[1], '  z:', p[2])
    print()

从上面的示例继续,一旦所有内容都按名称而不是按索引访问点组件,通过不更改任何索引号,可能更容易引入进一步的更改:

from collections import namedtuple


Point = namedtuple('Point', 'name x y z')  # addition of the field 'name'

origin = Point('O', 0, 0, 0)

A = Point('A', 1, 1, 1)
B = Point('B', 1, 1, 0)
C = Point('C', 1, 0, 0)
D = Point('D', 1, 0, 1)

for p in (origin, A, B, C, D):
    print(p)
    print(p.name)  # more readable than p[0] that is no more the x coordinate
    print('x:', p.x,  '  y:', p.y,  '  z:', p.z)  # unchanged
    print('x:', p[1], '  y:', p[2], '  z:', p[3])  # changed
    print()

枚举 是一种将符号名称与常量值耦合并class将它们确定为特定集合的方法。我们通过创建从 EnumIntEnum 派生的 class 来定义枚举,具体取决于我们希望常量具有的值: Enum 是通用版本,IntEnum 强制每个常量值都是 int 类型。

例如,枚举非常适合按名称、特定整数类型、性别定义颜色,或者,更一般地说,属于特定集合的元素。

from enum import Enum, IntEnum, unique

class Color_1(Enum):
    red = 'red'
    green = 'green'
    blue = 'blue'

class Color_2(Enum):
    red = (255, 0, 0)
    green = (0, 255, 0)
    blue = (0, 0, 255)

class Color_3(IntEnum):
    red = 0xFF0000
    green = 0xFF00
    blue = 0xFF

class Gender_1(Enum):
    unknown = 'U'
    male = 'M'
    female = 'F'

class Gender_2(Enum):
    unknown = 0.3
    male = 0.5
    female = 0.7

class Shape(Enum):  # Note the different constants types, perfectly legal
    TRIANGLE = 't'
    RECTANGLE = 5
    SQUARE = tuple('square')

class DataType(IntEnum):
    int8 = -8
    int16 = -16
    int32 = -32
    int64 = -64
    int = -2
    negative = -1
    positive = 1
    uint = 2
    uint8 = 8
    uint16 = 16
    uint32 = 32
    uint64 = 64

在 pythonic 开发中 - 枚举元素可能具有指定的特定值 - 可以是唯一的,也可以不是唯一的,具体取决于您的偏好和规范。 unique 装饰器用于强制值的唯一性。默认情况下,可以将相同的常量值分配给两个或多个不同的符号名称。

class Color_4(IntEnum):
    red = 1
    green = 2
    blue = 3
    RED = 1
    GREEN = 2
    BLUE = 3

枚举元素可以相互比较,但要想成功,不仅值要匹配,就连它们的类型也必须相同。

例如:

Color_4.red == Color_4.RED

将 return 为真(相同 class,相同的值),但以下内容:

Shape.SQUARE == tuple('square')

将为 False - 因为比较的正确元素 - tuple('square') - 不是 Shape 类型,尽管它们具有相同的值。

总而言之,枚举和命名元组是不同的工具。

最近刚刚将枚举添加到 Python(搜索 PEP435)。如果没记错的话,namedtuples 已经存在很长时间了,但我仍然是社区新手,因此我可能是错的。 HTH