枚举和命名元组有什么区别?

41

我想知道枚举和命名元组之间的区别以及何时应该使用其中之一。


1
OP在提问之前应该先多做一些搜索。可以看看这些话题:https://dev59.com/53A85IYBdhLWcg3wCe9Z 和 https://dev59.com/FHVD5IYBdhLWcg3wQZUg - Kiogara
2个回答

36

这只是类比(尽管不完美),你可以把Python中的enum.Enumnamedtuple看作是C语言中的enumstruct。换句话说,enum是一种给值取别名的方式,而namedtuple则是一种按名称封装数据的方式。两者并不真正可互换,但你可以将enum用作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)
您可以像访问常规对象一样访问人员的命名“属性”。
>>> 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

然而,一个namedtuple和一个class对象之间的主要区别是,namedtuple的属性在创建后不能被更改。


3
你也可以使用 namedtuple 作为枚举的值... class People(enum.Enum): john = Person('John', 21, HairColor.blonde) - Bakuriu
似乎namedtuple就像Java中的enum,是吗? - Marco Sulla
那么enum有用处是为了消除魔法数字?我很难看到好的使用案例。在PEP 435中,它说:“这可以让我们用友好的字符串表示形式的枚举替换标准库中的许多整数常量,而不会放弃向后兼容性。” - Alex Povel
1
@Alex,据我所知,enum对于避免歧义和提高性能(存储int而不是更大的str)非常有用。我可以通过制表符自动完成可用的枚举值,而不必猜测字符串版本是什么(是驼峰?全大写?小写?-->使用枚举来避免这种情况)。当你处理大数据表时,你可以存储较小的整数,而不是存储10^8个字符串 :) - 123

23
namedtuple 是一种快速的结构,使用 __slots__ 而不是 __dict__ ,在初始化时确定您提供的内容(实际上变成只读),尽管存在一个 _replace() 方法。
通常使用 namedtuple 时需要大量相同类型(如数百、数千乃至数百万)的对象,或者读取和/或写入记录。
例如,经常被引用的示例是 Point namedtuple,可能用于处理其 x, y, z 组件的多边形顶点。
与通过索引(0、1、2 等)指向正确组件相比,命名元组引入的开销很小。使用名称(.x、.y、.z 等)更容易阅读,即使是在编写代码几个月后,对其他程序员也更好。
因此,命名元组快速,可用于有意义地标识元组的内容,最重要的是可以与通过索引访问元组内容的旧代码共存。
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()

枚举是一种将符号名称与常量值耦合并将它们分类为特定集合的方法。我们通过创建一个派生自EnumIntEnum的类来定义枚举,具体取决于我们想要给常量赋什么值: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

在Python开发中,枚举元素可以被赋予特定的值,这些值可以是唯一的,也可以不唯一,这取决于您的偏好和规范。使用unique修饰符来强制值的唯一性。默认情况下,可以将相同的常量值分配给两个或更多不同的符号名称。
class Color_4(IntEnum):
    red = 1
    green = 2
    blue = 3
    RED = 1
    GREEN = 2
    BLUE = 3

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

例如:

Color_4.red == Color_4.RED

将返回True(相同的类,相同的值),但以下内容:

Shape.SQUARE == tuple('square')

将会是False - 因为比较的右侧元素 - tuple('square') - 不属于Shape类型,尽管它们都具有相同的值。

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

枚举最近才被添加到Python中(搜索PEP435)。如果我没记错的话,命名元组已经可用了很长一段时间,但我仍然是社区的新手,所以可能会出错。 希望对你有帮助。


和枚举相比呢? - Billy
@Billy 抱歉,你来的时候我正在写第二部分,刚刚才添加的。 - Alberto Vassena
通过使用 IntEnum 进行测试,我注意到以下比较 Color_4.red == 1 的结果为 True。然而,当执行 1 in Color_4 时,结果为 False(只有在执行 Color_4.red in Color_4 时才会得到 True)。 - Marc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接