题 我怎样才能用Python代表'Enum'?


我主要是一名C#开发人员,但我目前正在使用Python开发一个项目。

我怎样才能在Python中表示Enum的等价物?


1146


起源




答案:


枚举已添加到Python 3.4中,如中所述 PEP 435。它也一直都是 向后移植到3.3,3.2,3.1,2.7,2.6,2.5和2.4 在pypi。

对于更高级的Enum技术,请尝试使用 aenum图书馆 (2.7,3.3 +,同一作者 enum34。代码在py2和py3之间不是完全兼容的,例如你需要 __order__ 在python 2中)。

  • 使用 enum34,做 $ pip install enum34
  • 使用 aenum,做 $ pip install aenum

安装 enum (没有数字)将安装一个完全不同和不兼容的版本。


from enum import Enum     # for enum34, or the stdlib version
# from aenum import Enum  # for the aenum version
Animal = Enum('Animal', 'ant bee cat dog')

Animal.ant  # returns <Animal.ant: 1>
Animal['ant']  # returns <Animal.ant: 1> (string lookup)
Animal.ant.name  # returns 'ant' (inverse lookup)

或等效地:

class Animal(Enum):
    ant = 1
    bee = 2
    cat = 3
    dog = 4

在早期版本中,完成枚举的一种方法是:

def enum(**enums):
    return type('Enum', (), enums)

这样使用:

>>> Numbers = enum(ONE=1, TWO=2, THREE='three')
>>> Numbers.ONE
1
>>> Numbers.TWO
2
>>> Numbers.THREE
'three'

您还可以使用以下内容轻松支持自动枚举:

def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    return type('Enum', (), enums)

并使用如下:

>>> Numbers = enum('ZERO', 'ONE', 'TWO')
>>> Numbers.ZERO
0
>>> Numbers.ONE
1

可以通过以下方式添加对将值转换回名称的支持:

def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    reverse = dict((value, key) for key, value in enums.iteritems())
    enums['reverse_mapping'] = reverse
    return type('Enum', (), enums)

这会覆盖具有该名称的任何内容,但它对于在输出中呈现您的枚举非常有用。如果反向映射不存在,它将抛出KeyError。第一个例子:

>>> Numbers.reverse_mapping['three']
'THREE'

2320



+1是一种非新颖的使用方法 type()  docs.python.org/library/functions.html#type - Zachary Young
巧妙的解决方案!我想知道在调试时是否/如何打印标识符的名称?例如,拥有 print "The value is %r" % Numbers.ONE 产量 The value is ONE 代替 The value is 1... - Pascal Bourque
请注意,这与C枚举并不是很相似,因为它不能有意义地返回由返回的类型的变量/对象。 enum。 - Marcin
哦,我注意到可以做出轻微的改进。 zip(x, range(len(x)) 是一种代码气味 - 它可以通过枚举来代替 enums = dict((x, i) for i, x in enumerate(sequential)), **named) 这不会构建zip / range所做的所有列表。 - Tom Swirly
这个问题 enum 包是它的枚举类型不能存在酸洗 - unpickling。创建一个enumaration Foo = Enum("foo", "bar"),然后比较 pickle.loads(pickle.dumps(Foo.foo)) == Foo.foo;结果是 False。当你有一个多处理程序时会很痛,因为看似相等的值会停止通过相等检查。 - sastanin


在PEP 435之前,Python没有等效的,但你可以实现自己的。

我自己,我喜欢保持简单(我在网上看过一些非常复杂的例子),像这样......

class Animal:
    DOG = 1
    CAT = 2

x = Animal.DOG

在Python 3.4中(PEP 435), 你(们)能做到 枚举 基类。这将为您提供一些额外的功能,如PEP中所述。例如,枚举成员与整数不同,它们由a组成 name 和a value

class Animal(Enum):
    DOG = 1
    CAT = 2

print(Animal.DOG)
# <Animal.DOG: 1>

print(Animal.DOG.value)
# 1

print(Animal.DOG.name)
# "DOG"

如果您不想键入值,请使用以下快捷方式:

class Animal(Enum):
    DOG, CAT = range(2)

Enum 实现 可以转换为列表并且可以迭代。其成员的顺序是声明顺序,与其值无关。例如:

class Animal(Enum):
    DOG = 1
    CAT = 2
    COW = 0

list(Animal)
# [<Animal.DOG: 1>, <Animal.CAT: 2>, <Animal.COW: 0>]

[animal.value for animal in Animal]
# [1, 2, 0]

Animal.CAT in Animal
# True

721



不,这是一个类变量。 - Georg Schölly
Python默认是动态的。没有正当理由在像Python这样的语言中强制执行编译时安全性,尤其是在没有Python的情况下。另一件事......一个好的模式只有在它创建的环境中才是好的。根据您使用的工具,良好的模式也可能被取代或完全无用。 - Alexandru Nedelcu
@Longpoke,如果你有100个值,那么你肯定做错了;)我喜欢与我的枚举相关的数字......它们易于编写(vs字符串),可以很容易地保存在数据库中,并且兼容C / C ++枚举,使编组更容易。 - Alexandru Nedelcu
我用这个,用数字代替 object()。 - Tobu
最初的PEP354不再仅仅被拒绝,而是现在被标记为已被取代。 PEP435为Python 3.4添加了标准的Enum。看到 python.org/dev/peps/pep-0435 - Peter Hansen


这是一个实现:

class Enum(set):
    def __getattr__(self, name):
        if name in self:
            return name
        raise AttributeError

这是它的用法:

Animals = Enum(["DOG", "CAT", "HORSE"])

print(Animals.DOG)

298



优秀。通过覆盖可以进一步改善这一点 __setattr__(self, name, value) 有可能 __delattr__(self, name) 所以,如果你不小心写 Animals.DOG = CAT,它不会默默地成功。 - Joonas Pulakka
@shahjapan:有趣但相对缓慢:对每个访问都进行了测试 Animals.DOG;另外,constats的值是字符串,因此与这些常量的比较比允许整数作为值更慢。 - Eric Lebigot
@shahjapan:我认为这个解决方案不像Alexandru或Mark的简短解决方案那么清晰。不过,这是一个有趣的解决方案。 :) - Eric Lebigot
我试过用 setattr() 功能里面 __init__() 方法而不是覆盖 __getattr__() 方法。我假设这应该以相同的方式工作:class Enum(object):def __init __(self,enum_string_list):if type(enum_string_list)== list:for enum_string in enum_string_list:setattr(self,enum_string,enum_string)else:raise AttributeError的 - Harshith J.V.
@AndréTerra:你如何检查a中的集合成员资格 try-except 块? - bgusach


如果您需要数值,这是最快的方法:

dog, cat, rabbit = range(3)

在Python 3.x中,您还可以在末尾添加一个带星号的占位符,这将占用该范围的所有剩余值,以防您不介意浪费内存并且无法计算:

dog, cat, rabbit, horse, *_ = range(100)

183





最适合您的解决方案取决于您的需求   enum

简单的枚举:

如果你需要的话 enum 仅作为列表  识别不同 项目,解决方案 马克哈里森 (上面)很棒:

Pen, Pencil, Eraser = range(0, 3)

用一个 range 也允许你设置任何 起始价值

Pen, Pencil, Eraser = range(9, 12)

除上述内容外,如果还要求项目属于a 容器 某种,然后将它们嵌入一个类:

class Stationery:
    Pen, Pencil, Eraser = range(0, 3)

要使用枚举项,您现在需要使用容器名称和项目名称:

stype = Stationery.Pen

复杂的枚举:

对于冗长的枚举列表或更复杂的枚举用法,这些解决方案是不够的。你可以看看Will Ware的配方 在Python中模拟枚举 发表在 Python Cookbook。该在线版本可用 这里

更多信息:

PEP 354:Python中的枚举 有一个关于Python中枚举的提议的有趣细节以及它被拒绝的原因。


119



同 range 你可以省略第一个参数,如果它是0 - ToonAlfrink
另一个适合某些目的的假枚举是 my_enum = dict(map(reversed, enumerate(str.split('Item0 Item1 Item2'))))。然后 my_enum 可以用于查找,例如, my_enum['Item0'] 可以是序列的索引。您可能想要包装结果 str.split 在一个函数中,如果有任何重复,则抛出异常。 - Ana Nimbus


在Java JDK 5之前使用的类型安全枚举模式有一个 一些优点。就像Alexandru的回答一样,你创造了一个 class和class level字段是枚举值;然而,枚举 值是类的实例而不是小整数。这有 你的枚举值不会无意中比较相等的优点 对于小整数,你可以控制它们的打印方式,随意添加 方法如果有用并使用isinstance进行断言:

class Animal:
   def __init__(self, name):
       self.name = name

   def __str__(self):
       return self.name

   def __repr__(self):
       return "<Animal: %s>" % self

Animal.DOG = Animal("dog")
Animal.CAT = Animal("cat")

>>> x = Animal.DOG
>>> x
<Animal: dog>
>>> x == 1
False

最近 python-dev上的线程 指出野外有几个枚举库,包括:


76



我认为这是一个非常糟糕的方法。 Animal.DOG = Animal(“dog”)Animal.DOG2 = Animal(“dog”)断言Animal.DOG == Animal.DOG2失败...... - Confusion
@Confusion用户不应该调用构造函数,事实上甚至构造函数都是一个实现细节,你必须与使用你的代码的人沟通,使新的枚举值没有意义,并且退出的代码不会“做正确的事”。当然,这并不能阻止你实现Animal.from_name(“dog”) - > Animal.DOG。 - Aaron Maenpaa
“你的枚举值不会无意中比较等于小整数的优势”这有什么好处?将枚举与整数进行比较有什么问题?特别是如果将枚举存储在数据库中,通常希望将其存储为整数,因此您必须在某些时候将其与整数进行比较。 - ibz
@Aaaron Maenpaa。正确。它仍然是一种破碎而过于复杂的方式。 - aaronasterling
@AaronMcSmooth这真的取决于你是从C语言进入“Enums只是几个整数的名字”还是更加面向对象的方法,其中枚举值是实际对象并且有方法(这是Java中的枚举方式) 1.5是,和类型安全枚举模式的目的)。就个人而言,我不喜欢switch语句,因此我倾向于实际对象的枚举值。 - Aaron Maenpaa


Enum类可以是单行。

class Enum(tuple): __getattr__ = tuple.index

如何使用它(正向和反向查找,键,值,项目等)

>>> State = Enum(['Unclaimed', 'Claimed'])
>>> State.Claimed
1
>>> State[1]
'Claimed'
>>> State
('Unclaimed', 'Claimed')
>>> range(len(State))
[0, 1]
>>> [(k, State[k]) for k in range(len(State))]
[(0, 'Unclaimed'), (1, 'Claimed')]
>>> [(k, getattr(State, k)) for k in State]
[('Unclaimed', 0), ('Claimed', 1)]

56



我认为这是最简单的最优雅的解决方案。在python 2.4(是的,旧的遗留服务器)中,元组没有索引。我用列表解决了替换问题。 - Massimo
我在一个Jupyter笔记本中尝试过这个,发现它不能作为单行定义工作,而是将它放在一起 GETATTR 第二个(缩进)行的定义将被接受。 - user5920660


Python没有内置的等价物 enum,和其他答案有实现自己的想法(你可能也有兴趣 超过顶级版本 在Python食谱中)。

但是,在一个 enum 在C中会被要求,我通常会结束 只使用简单的字符串:由于实现了对象/属性的方式,(C)Python已经过优化,无论如何都可以使用短字符串快速工作,因此使用整数实际上没有任何性能优势。为了防止打字错误/无效值,您可以在选定位置插入支票。

ANIMALS = ['cat', 'dog', 'python']

def take_for_a_walk(animal):
    assert animal in ANIMALS
    ...

(与使用课程相比,一个缺点是你失去了自动完成的好处)


44



我更喜欢这个解决方案我喜欢尽可能使用内置类型。 - Seun Osewa
那个版本并不是真正的顶级版本。它只提供了大量的测试代码 - Casebash
实际上,“正确”版本在评论中并且要复杂得多 - 主要版本有一个小错误。 - Casebash


所以,我同意。我们不要在Python中强制使用类型安全,但我想保护自己免受愚蠢的错误。那么我们怎么看待这个呢?

class Animal(object):
    values = ['Horse','Dog','Cat']

    class __metaclass__(type):
        def __getattr__(self, name):
            return self.values.index(name)

在定义我的枚举时,它让我免于价值冲突。

>>> Animal.Cat
2

还有另一个方便的优点:真正快速的反向查找:

def name_of(self, i):
    return self.values[i]

44



我喜欢这个,但是你可以用一个元组来锁定效率值?我玩了它并想出了一个版本,它从args中设置self.values 在里面。很高兴能够申报 Animal = Enum('horse', 'dog', 'cat')。我也抓住了ValueError GETATTR 如果self.values中缺少项目 - 最好使用提供的名称字符串来引发AttributeError。基于我在该领域的有限知识,我无法让元类在Python 2.7中工作,但我的自定义Enum类可以使用直接实例方法。 - trojjer


在2013-05-10,Guido同意接受 PEP 435 进入Python 3.4标准库。这意味着Python最终内置了对枚举的支持!

Python 3.3,3.2,3.1,2.7,2.6,2.5和2.4提供了一个后端端口。它在Pypi上 enum34

宣言:

>>> from enum import Enum
>>> class Color(Enum):
...     red = 1
...     green = 2
...     blue = 3

表示:

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

迭代:

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

程序化访问:

>>> Color(1)
Color.red
>>> Color['blue']
Color.blue

有关更多信息,请参阅 提案。官方文档很快就会发布。


30