如何在Python中表示“枚举”?

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

如何表示Python中Enum的等价物?

1209975 次浏览

在PEP 435之前,Python没有等价物,但您可以实现自己的。

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

class Animal:DOG = 1CAT = 2
x = Animal.DOG

在Python 3.4(PEP 435)中,您可以将enum作为基类。这为您提供了一些额外的功能,在PEP中进行了描述。例如,枚举成员不同于整数,它们由namevalue组成。

from enum import Enum
class Animal(Enum):DOG = 1CAT = 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 = 1CAT = 2COW = 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

嗯……我想最接近枚举的是字典,定义如下:

months = {'January': 1,'February': 2,...}

months = dict(January=1,February=2,...)

然后,您可以像这样为常量使用符号名称:

mymonth = months['January']

还有其他选项,例如元组列表或元组元组,但字典是唯一为您提供“符号”(常量字符串)方式来访问值。

编辑:我也喜欢Alexandru的回答!

Python没有与enum等价的内置函数,其他答案有实现你自己的想法(你可能也对Python食谱中的超过最高版本感兴趣)。

然而,在C中调用enum的情况下,我通常以只使用简单的字符串结束:由于对象/属性的实现方式,(C)Python被优化为使用短字符串非常快,所以使用整数不会有任何性能优势。为了防止拼写错误/无效值,你可以在选定的地方插入检查。

ANIMALS = ['cat', 'dog', 'python']
def take_for_a_walk(animal):assert animal in ANIMALS...

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

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

dog, cat, rabbit = range(3)

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

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

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

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 == 1False

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

David建议使用dicts。我想更进一步,使用集合:

months = set('January', 'February', ..., 'December')

现在你可以测试一个值是否与集合中的一个值匹配,如下所示:

if m in months:

不过,像dF一样,我通常只使用字符串常量来代替枚举。

Alexandru关于为枚举使用类常量的建议效果很好。

我还喜欢为每一组常量添加一个字典,以查找人类可读的字符串表示。

这有两个目的:a)它提供了一种简单的方法来漂亮地打印枚举;b)字典在逻辑上对常量进行分组,以便您可以测试成员资格。

class Animal:TYPE_DOG = 1TYPE_CAT = 2
type2str = {TYPE_DOG: "dog",TYPE_CAT: "cat"}
def __init__(self, type_):assert type_ in self.type2str.keys()self._type = type_
def __repr__(self):return "<%s type=%s>" % (self.__class__.__name__, self.type2str[self._type].upper())
def M_add_class_attribs(attribs):def foo(name, bases, dict_):for v, k in attribs:dict_[k] = vreturn type(name, bases, dict_)return foo
def enum(*names):class Foo(object):__metaclass__ = M_add_class_attribs(enumerate(names))def __setattr__(self, name, value):  # this makes it read-onlyraise NotImplementedErrorreturn Foo()

像这样使用它:

Animal = enum('DOG', 'CAT')Animal.DOG # returns 0Animal.CAT # returns 1Animal.DOG = 2 # raises NotImplementedError

如果您只想要唯一的符号并且不关心值,请替换以下行:

__metaclass__ = M_add_class_attribs(enumerate(names))

用这个:

__metaclass__ = M_add_class_attribs((object(), name) for name in names)

有趣的是,前几天我只是需要这个,但我找不到值得使用的实现……所以我写了自己的:

import functools
class EnumValue(object):def __init__(self,name,value,type):self.__value=valueself.__name=nameself.Type=typedef __str__(self):return self.__namedef __repr__(self):#2.6 only... so change to what ever you need...return '{cls}({0!r},{1!r},{2})'.format(self.__name,self.__value,self.Type.__name__,cls=type(self).__name__)
def __hash__(self):return hash(self.__value)def __nonzero__(self):return bool(self.__value)def __cmp__(self,other):if isinstance(other,EnumValue):return cmp(self.__value,other.__value)else:return cmp(self.__value,other)#hopefully their the same type... but who cares?def __or__(self,other):if other is None:return selfelif type(self) is not type(other):raise TypeError()return EnumValue('{0.Name} | {1.Name}'.format(self,other),self.Value|other.Value,self.Type)def __and__(self,other):if other is None:return selfelif type(self) is not type(other):raise TypeError()return EnumValue('{0.Name} & {1.Name}'.format(self,other),self.Value&other.Value,self.Type)def __contains__(self,other):if self.Value==other.Value:return Truereturn bool(self&other)def __invert__(self):enumerables=self.Type.__enumerables__return functools.reduce(EnumValue.__or__,(enum for enum in enumerables.itervalues() if enum not in self))
@propertydef Name(self):return self.__name
@propertydef Value(self):return self.__value
class EnumMeta(type):@staticmethoddef __addToReverseLookup(rev,value,newKeys,nextIter,force=True):if value in rev:forced,items=rev.get(value,(force,()) )if forced and force: #value was forced, so just appendrev[value]=(True,items+newKeys)elif not forced:#move it to a new spotnext=nextIter.next()EnumMeta.__addToReverseLookup(rev,next,items,nextIter,False)rev[value]=(force,newKeys)else: #not forcing this valuenext = nextIter.next()EnumMeta.__addToReverseLookup(rev,next,newKeys,nextIter,False)rev[value]=(force,newKeys)else:#set it and forget itrev[value]=(force,newKeys)return value
def __init__(cls,name,bases,atts):classVars=vars(cls)enums = classVars.get('__enumerables__',None)nextIter = getattr(cls,'__nextitr__',itertools.count)()reverseLookup={}values={}
if enums is not None:#build reverse lookupfor item in enums:if isinstance(item,(tuple,list)):items=list(item)value=items.pop()EnumMeta.__addToReverseLookup(reverseLookup,value,tuple(map(str,items)),nextIter)else:value=nextIter.next()value=EnumMeta.__addToReverseLookup(reverseLookup,value,(str(item),),nextIter,False)#add it to the reverse lookup, but don't force it to that value
#build values and clean up reverse lookupfor value,fkeys in reverseLookup.iteritems():f,keys=fkeysfor key in keys:enum=EnumValue(key,value,cls)setattr(cls,key,enum)values[key]=enumreverseLookup[value]=tuple(val for val in values.itervalues() if val.Value == value)setattr(cls,'__reverseLookup__',reverseLookup)setattr(cls,'__enumerables__',values)setattr(cls,'_Max',max([key for key in reverseLookup] or [0]))return super(EnumMeta,cls).__init__(name,bases,atts)
def __iter__(cls):for enum in cls.__enumerables__.itervalues():yield enumdef GetEnumByName(cls,name):return cls.__enumerables__.get(name,None)def GetEnumByValue(cls,value):return cls.__reverseLookup__.get(value,(None,))[0]
class Enum(object):__metaclass__=EnumMeta__enumerables__=None
class FlagEnum(Enum):@staticmethoddef __nextitr__():yield 0for val in itertools.count():yield 2**val
def enum(name,*args):return EnumMeta(name,(Enum,),dict(__enumerables__=args))

接受或离开它,它做了我需要它做的事情:)

像这样使用它:

class Air(FlagEnum):__enumerables__=('None','Oxygen','Nitrogen','Hydrogen')
class Mammals(Enum):__enumerables__=('Bat','Whale',('Dog','Puppy',1),'Cat')Bool = enum('Bool','Yes',('No',0))

我使用什么:

class Enum(object):def __init__(self, names, separator=None):self.names = names.split(separator)for value, name in enumerate(self.names):setattr(self, name.upper(), value)def tuples(self):return tuple(enumerate(self.names))

如何使用:

>>> state = Enum('draft published retracted')>>> state.DRAFT0>>> state.RETRACTED2>>> state.FOOTraceback (most recent call last):File "<stdin>", line 1, in <module>AttributeError: 'Enum' object has no attribute 'FOO'>>> state.tuples()((0, 'draft'), (1, 'published'), (2, 'retracted'))

因此,这为您提供了state.PUBLISHED等整数常量和在Django模型中用作选项的二元组。

对你来说,最好的解决方案取决于你对假的#0的要求。

简单枚举:

如果您只需要#0作为名字的列表来标识不同的项目,那么马克·哈里森(上面)的解决方案很棒:

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

使用#0还允许您设置任何起始值

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

除此之外,如果您还要求项目属于某种类型的容器,则将它们嵌入到类中:

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

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

stype = Stationery.Pen

复杂枚举:

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

更多信息:

PEP 354:Python中的枚举有Python中枚举提案的有趣细节以及它被拒绝的原因。

使用以下。

TYPE = {'EAN13':   u'EAN-13','CODE39':  u'Code 39','CODE128': u'Code 128','i25':     u'Interleaved 2 of 5',}
>>> TYPE.items()[('EAN13', u'EAN-13'), ('i25', u'Interleaved 2 of 5'), ('CODE39', u'Code 39'), ('CODE128', u'Code 128')]>>> TYPE.keys()['EAN13', 'i25', 'CODE39', 'CODE128']>>> TYPE.values()[u'EAN-13', u'Interleaved 2 of 5', u'Code 39', u'Code 128']

我在Django模型选择中使用了它,它看起来非常Pythonic。它不是真正的Enum,但它确实起到了作用。

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

对于更高级的枚举技术,请尝试aenum库(2.7,3.3+,与enum34相同的作者。Py2和py3之间的代码不完全兼容,例如您需要#1在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 versionAnimal = 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 = 1bee = 2cat = 3dog = 4

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

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

它是这样使用的:

>>> Numbers = enum(ONE=1, TWO=2, THREE='three')>>> Numbers.ONE1>>> Numbers.TWO2>>> 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.ZERO0>>> Numbers.ONE1

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

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'] = reversereturn type('Enum', (), enums)

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

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

如果您使用MyPy,另一种表达“枚举”的方式是使用#0

例如:

from typing import Literal #python >=3.8from typing_extensions import Literal #python 2.7, 3.4-3.7

Animal = Literal['ant', 'bee', 'cat', 'dog']
def hello_animal(animal: Animal):print(f"hello {animal}")
hello_animal('rock') # errorhello_animal('bee') # passes

我在pyparsing中需要一些符号常量来表示二元运算符的左右结合性。我使用了这样的类常量:

# an internal class, not intended to be seen by client codeclass _Constants(object):pass

# an enumeration of constants for operator associativityopAssoc = _Constants()opAssoc.LEFT = object()opAssoc.RIGHT = object()

现在,当客户端代码想要使用这些常量时,他们可以使用以下命令导入整个枚举:

import opAssoc from pyparsing

枚举是唯一的,它们可以用'is'而不是 '==', 它们不会在我的代码中为一个次要的概念占用很大的空间,并且它们很容易被导入到客户端代码中。它们不支持任何花哨的str()行为,但到目前为止这属于YAGNI类别。

这是我见过的最好的:“Python中的First Class Enums”

http://code.activestate.com/recipes/413486/

它给你一个类,这个类包含所有的枚举。枚举可以相互比较,但没有任何特定的值;你不能将它们用作整数值。(一开始我反对这样做,因为我习惯了C枚举,它们是整数值。但是如果你不能将它用作整数,你就不能错误地将它用作整数,所以总的来说我认为这是一个胜利。)每个枚举都是一个唯一的值。你可以打印枚举,你可以迭代它们,你可以测试枚举值是否“在”枚举中。它非常完整和光滑。

编辑(cfi):上面的链接与Python 3不兼容。这是我对Python 3的enum.py端口:

def cmp(a,b):if a < b: return -1if b < a: return 1return 0

def Enum(*names):##assert names, "Empty enums are not supported" # <- Don't like empty enums? Uncomment!
class EnumClass(object):__slots__ = namesdef __iter__(self):        return iter(constants)def __len__(self):         return len(constants)def __getitem__(self, i):  return constants[i]def __repr__(self):        return 'Enum' + str(names)def __str__(self):         return 'enum ' + str(constants)
class EnumValue(object):__slots__ = ('__value')def __init__(self, value): self.__value = valueValue = property(lambda self: self.__value)EnumType = property(lambda self: EnumType)def __hash__(self):        return hash(self.__value)def __cmp__(self, other):# C fans might want to remove the following assertion# to make all enums comparable by ordinal value {;))assert self.EnumType is other.EnumType, "Only values from the same enum are comparable"return cmp(self.__value, other.__value)def __lt__(self, other):   return self.__cmp__(other) < 0def __eq__(self, other):   return self.__cmp__(other) == 0def __invert__(self):      return constants[maximum - self.__value]def __nonzero__(self):     return bool(self.__value)def __repr__(self):        return str(names[self.__value])
maximum = len(names) - 1constants = [None] * len(names)for i, each in enumerate(names):val = EnumValue(i)setattr(EnumClass, each, val)constants[i] = valconstants = tuple(constants)EnumType = EnumClass()return EnumType

if __name__ == '__main__':print( '\n*** Enum Demo ***')print( '--- Days of week ---')Days = Enum('Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su')print( Days)print( Days.Mo)print( Days.Fr)print( Days.Mo < Days.Fr)print( list(Days))for each in Days:print( 'Day:', each)print( '--- Yes/No ---')Confirmation = Enum('No', 'Yes')answer = Confirmation.Noprint( 'Your answer is not', ~answer)

这里有一个实现:

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

以下是它的用法:

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

在Aaron Maenpaa提出的类似枚举的Java实现之后,我提出了以下内容。

class Enum:#'''#Java like implementation for enums.##Usage:#class Tool(Enum): name = 'Tool'#Tool.DRILL = Tool.register('drill')#Tool.HAMMER = Tool.register('hammer')#Tool.WRENCH = Tool.register('wrench')#'''
name = 'Enum'    # Enum name_reg = dict([])   # Enum registered values
@classmethoddef register(cls, value):#'''#Registers a new value in this enum.##@param value: New enum value.##@return: New value wrapper instance.#'''inst = cls(value)cls._reg[value] = instreturn inst
@classmethoddef parse(cls, value):#'''#Parses a value, returning the enum instance.##@param value: Enum value.##@return: Value corresp instance.#'''return cls._reg.get(value)
def __init__(self, value):#'''#Constructor (only for internal use).#'''self.value = value
def __str__(self):#'''#str() overload.#'''return self.value
def __repr__(self):#'''#repr() overload.#'''return "<" + self.name + ": " + self.value + ">"

PyPI的枚举包提供了枚举的健壮实现。之前的回答提到了PEP 354;这被拒绝了,但该提案得到了实现http://pypi.python.org/pypi/enum.

用法简单而优雅:

>>> from enum import Enum>>> Colors = Enum('red', 'blue', 'green')>>> shirt_color = Colors.green>>> shirt_color = Colors[2]>>> shirt_color > Colors.redTrue>>> shirt_color.index2>>> str(shirt_color)'green'

为什么枚举必须是整数?不幸的是,如果不更改Python语言,我想不出任何好看的构造来生成这个,所以我将使用字符串:

class Enumerator(object):def __init__(self, name):self.name = name
def __eq__(self, other):if self.name == other:return Truereturn self is other
def __ne__(self, other):if self.name != other:return Falsereturn self is other
def __repr__(self):return 'Enumerator({0})'.format(self.name)
def __str__(self):return self.name
class Enum(object):def __init__(self, *enumerators):for e in enumerators:setattr(self, e, Enumerator(e))def __getitem__(self, key):return getattr(self, key)

再一次,为了配置文件或其他远程输入,我们可以自然地对字符串进行测试,这可能会更好。

示例:

class Cow(object):State = Enum('standing','walking','eating','mooing','sleeping','dead','dying')state = State.standing
In [1]: from enum import Enum
In [2]: c = Cow()
In [3]: c2 = Cow()
In [4]: c.state, c2.stateOut[4]: (Enumerator(standing), Enumerator(standing))
In [5]: c.state == c2.stateOut[5]: True
In [6]: c.State.mooingOut[6]: Enumerator(mooing)
In [7]: c.State['mooing']Out[7]: Enumerator(mooing)
In [8]: c.state = Cow.State.dead
In [9]: c.state == c2.stateOut[9]: False
In [10]: c.state == Cow.State.deadOut[10]: True
In [11]: c.state == 'dead'Out[11]: True
In [12]: c.state == Cow.State['dead']Out[11]: True
def enum( *names ):
'''Makes enum.Usage:E = enum( 'YOUR', 'KEYS', 'HERE' )print( E.HERE )'''
class Enum():passfor index, name in enumerate( names ):setattr( Enum, name, index )return Enum

我喜欢Java枚举,这就是我在Python中的做法:

def enum(clsdef):class Enum(object):__slots__=tuple([var for var in clsdef.__dict__ if isinstance((getattr(clsdef, var)), tuple) and not var.startswith('__')])
def __new__(cls, *args, **kwargs):if not '_the_instance' in cls.__dict__:cls._the_instance = object.__new__(cls, *args, **kwargs)return cls._the_instance
def __init__(self):clsdef.values=lambda cls, e=Enum: e.values()clsdef.valueOf=lambda cls, n, e=self: e.valueOf(n)for ordinal, key in enumerate(self.__class__.__slots__):args=getattr(clsdef, key)instance=clsdef(*args)instance._name=keyinstance._ordinal=ordinalsetattr(self, key, instance)
@classmethoddef values(cls):if not hasattr(cls, '_values'):cls._values=[getattr(cls, name) for name in cls.__slots__]return cls._values
def valueOf(self, name):return getattr(self, name)
def __repr__(self):return ''.join(['<class Enum (', clsdef.__name__, ') at ', str(hex(id(self))), '>'])
return Enum()

样品使用:

i=2@enumclass Test(object):A=("a",1)B=("b",)C=("c",2)D=tuple()E=("e",3)
while True:try:F, G, H, I, J, K, L, M, N, O=[tuple() for _ in range(i)]break;except ValueError:i+=1
def __init__(self, name="default", aparam=0):self.name=nameself.avalue=aparam

所有类变量都定义为元组,就像构造函数一样。到目前为止,您不能使用命名参数。

所以,我同意。让我们不要在Python中强制执行类型安全,但我想保护自己免受愚蠢的错误。那么我们对此有什么看法?

class Animal(object):values = ['Horse','Dog','Cat']
class __metaclass__(type):def __getattr__(self, name):return self.values.index(name)

它使我在定义枚举时避免了值冲突。

>>> Animal.Cat2

还有另一个方便的优势:非常快速的反向查找:

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

我更喜欢在Python中定义枚举,如下所示:

class Animal:class Dog: passclass Cat: pass
x = Animal.Dog

它比使用整数更bug,因为你不必担心确保整数是唯一的(例如,如果你说Dog=1和Cat=1,你就完蛋了)。

它比使用字符串更bug,因为你不必担心拼写错误(例如:x=="catt"静默失败,但x==动物. Catt是一个运行时异常)。


附录:您甚至可以通过让Dog和Cat从具有正确元类的符号类继承来增强此解决方案:

class SymbolClass(type):def __repr__(self): return self.__qualname__def __str__(self): return self.__name__
class Symbol(metaclass=SymbolClass): pass

class Animal:class Dog(Symbol): passclass Cat(Symbol): pass

然后,如果您使用这些值来索引字典,请求它的表示将使它们显示得很好:

>>> mydict = {Animal.Dog: 'Wan Wan', Animal.Cat: 'Nyaa'}>>> mydict{Animal.Dog: 'Wan Wan', Animal.Cat: 'Nyaa'}

这是Alec Thomas的解决方案的一个变体:

def enum(*args, **kwargs):return type('Enum', (), dict((y, x) for x, y in enumerate(args), **kwargs))
x = enum('POOH', 'TIGGER', 'EEYORE', 'ROO', 'PIGLET', 'RABBIT', 'OWL')assert x.POOH == 0assert x.TIGGER == 1

另一个非常简单的Python枚举实现,使用namedtuple

from collections import namedtuple
def enum(*keys):return namedtuple('Enum', keys)(*keys)
MyEnum = enum('FOO', 'BAR', 'BAZ')

或者,或者,

# With sequential number valuesdef enum(*keys):return namedtuple('Enum', keys)(*range(len(keys)))
# From a dict / keyword argsdef enum(**kwargs):return namedtuple('Enum', kwargs.keys())(*kwargs.values())



# Example for dictionary param:values = {"Salad": 20, "Carrot": 99, "Tomato": "No i'm not"}Vegetables= enum(**values)
# >>> print(Vegetables.Tomato)        'No i'm not'

# Example for keyworded params:Fruits = enum(Apple="Steve Jobs", Peach=1, Banana=2)
# >>> print(Fruits.Apple)             'Steve Jobs'

与子类set上面的方法一样,这允许:

'FOO' in MyEnumother = MyEnum.FOOassert other == MyEnum.FOO

但具有更大的灵活性,因为它可以有不同的键和值。这允许

MyEnum.FOO < MyEnum.BAR

如果使用填充顺序数字值的版本,则按预期操作。

此解决方案是获取定义为列表的枚举类的简单方法(不再有烦人的整数赋值):

enumeration.py:

import new
def create(class_name, names):return new.classobj(class_name, (object,), dict((y, x) for x, y in enumerate(names)))

example.py:

import enumeration
Colors = enumeration.create('Colors', ('red','orange','yellow','green','blue','violet',))

为了解码二进制文件格式,我有机会需要一个枚举类。我碰巧想要的功能是简洁的枚举定义,能够通过整数值或字符串自由创建枚举实例,以及一个有用的represtions。这是我最终得到的:

>>> class Enum(int):...     def __new__(cls, value):...         if isinstance(value, str):...             return getattr(cls, value)...         elif isinstance(value, int):...             return cls.__index[value]...     def __str__(self): return self.__name...     def __repr__(self): return "%s.%s" % (type(self).__name__, self.__name)...     class __metaclass__(type):...         def __new__(mcls, name, bases, attrs):...             attrs['__slots__'] = ['_Enum__name']...             cls = type.__new__(mcls, name, bases, attrs)...             cls._Enum__index = _index = {}...             for base in reversed(bases):...                 if hasattr(base, '_Enum__index'):...                     _index.update(base._Enum__index)...             # create all of the instances of the new class...             for attr in attrs.keys():...                 value = attrs[attr]...                 if isinstance(value, int):...                     evalue = int.__new__(cls, value)...                     evalue._Enum__name = attr...                     _index[value] = evalue...                     setattr(cls, attr, evalue)...             return cls...

一个使用它的异想天开的例子:

>>> class Citrus(Enum):...     Lemon = 1...     Lime = 2...>>> Citrus.LemonCitrus.Lemon>>>>>> Citrus(1)Citrus.Lemon>>> Citrus(5)Traceback (most recent call last):File "<stdin>", line 1, in <module>File "<stdin>", line 6, in __new__KeyError: 5>>> class Fruit(Citrus):...     Apple = 3...     Banana = 4...>>> Fruit.AppleFruit.Apple>>> Fruit.LemonCitrus.Lemon>>> Fruit(1)Citrus.Lemon>>> Fruit(3)Fruit.Apple>>> "%d %s %r" % ((Fruit.Apple,)*3)'3 Apple Fruit.Apple'>>> Fruit(1) is Citrus.LemonTrue

主要特点:

  • str()int()repr()都会产生最有用的输出,分别是枚举的名称、其整数值和返回枚举的Python表达式。
  • 构造函数返回的枚举值严格限制为预定义值,没有意外枚举值。
  • 枚举值是单例;它们可以与is严格比较

我真的很喜欢Alec Thomas的解决方案(http://stackoverflow.com/a/1695250):

def enum(**enums):'''simple constant "enums"'''return type('Enum', (object,), enums)

它看起来优雅干净,但它只是一个创建具有指定属性的类的函数。

通过对函数的一点修改,我们可以让它更多地执行'enumy':

注意:我创建了以下示例,尝试重现pygtk的新样式“枚举”的行为(如Gtk.MessageType.WARNING)

def enum_base(t, **enums):'''enums with a base class'''T = type('Enum', (t,), {})for key,val in enums.items():setattr(T, key, T(val))
return T

这将创建一个基于指定类型的枚举。除了像前面的函数一样提供属性访问权限外,它的行为与您期望枚举对类型的行为一样。它还继承了基类。

例如,整数枚举:

>>> Numbers = enum_base(int, ONE=1, TWO=2, THREE=3)>>> Numbers.ONE1>>> x = Numbers.TWO>>> 10 + x12>>> type(Numbers)<type 'type'>>>> type(Numbers.ONE)<class 'Enum'>>>> isinstance(x, Numbers)True

使用此方法可以完成的另一个有趣的事情是通过覆盖内置方法来自定义特定行为:

def enum_repr(t, **enums):'''enums with a base class and repr() output'''class Enum(t):def __repr__(self):return '<enum {0} of type Enum({1})>'.format(self._name, t.__name__)
for key,val in enums.items():i = Enum(val)i._name = keysetattr(Enum, key, i)
return Enum


>>> Numbers = enum_repr(int, ONE=1, TWO=2, THREE=3)>>> repr(Numbers.ONE)'<enum ONE of type Enum(int)>'>>> str(Numbers.ONE)'1'

Enum类可以是单行的。

class Enum(tuple): __getattr__ = tuple.index

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

>>> State = Enum(['Unclaimed', 'Claimed'])>>> State.Claimed1>>> 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)]

我使用元类来实现枚举(在我看来,它是一个const)。这是代码:

class ConstMeta(type):'''Metaclass for some class that store constants'''def __init__(cls, name, bases, dct):'''init class instance'''def static_attrs():'''@rtype: (static_attrs, static_val_set)@return: Static attributes in dict format and static value set'''import typesattrs = {}val_set = set()#Maybe morefilter_names = set(['__doc__', '__init__', '__metaclass__', '__module__', '__main__'])for key, value in dct.iteritems():if type(value) != types.FunctionType and key not in filter_names:if len(value) != 2:raise NotImplementedError('not support for values that is not 2 elements!')#Check value[0] duplication.if value[0] not in val_set:val_set.add(value[0])else:raise KeyError("%s 's key: %s is duplicated!" % (dict([(key, value)]), value[0]))attrs[key] = valuereturn attrs, val_set
attrs, val_set = static_attrs()#Set STATIC_ATTRS to class instance so that can reusesetattr(cls, 'STATIC_ATTRS', attrs)setattr(cls, 'static_val_set', val_set)super(ConstMeta, cls).__init__(name, bases, dct)
def __getattribute__(cls, name):'''Rewrite the special function so as to get correct attribute value'''static_attrs = object.__getattribute__(cls, 'STATIC_ATTRS')if name in static_attrs:return static_attrs[name][0]return object.__getattribute__(cls, name)
def static_values(cls):'''Put values in static attribute into a list, use the function to validate value.@return: Set of values'''return cls.static_val_set
def __getitem__(cls, key):'''Rewrite to make syntax SomeConstClass[key] works, and return desc string of related static value.@return: Desc string of related static value'''for k, v in cls.STATIC_ATTRS.iteritems():if v[0] == key:return v[1]raise KeyError('Key: %s does not exists in %s !' % (str(key), repr(cls)))

class Const(object):'''Base class for constant class.
@usage:
Definition: (must inherit from Const class!>>> class SomeConst(Const):>>>   STATUS_NAME_1 = (1, 'desc for the status1')>>>   STATUS_NAME_2 = (2, 'desc for the status2')
Invoke(base upper SomeConst class):1) SomeConst.STATUS_NAME_1 returns 12) SomeConst[1] returns 'desc for the status1'3) SomeConst.STATIC_ATTRS returns {'STATUS_NAME_1': (1, 'desc for the status1'), 'STATUS_NAME_2': (2, 'desc for the status2')}4) SomeConst.static_values() returns set([1, 2])
Attention:SomeCosnt's value 1, 2 can not be duplicated!If WrongConst is like this, it will raise KeyError:class WrongConst(Const):STATUS_NAME_1 = (1, 'desc for the status1')STATUS_NAME_2 = (1, 'desc for the status2')'''__metaclass__ = ConstMeta###################################################################Const Base Class ends##################################################################

def main():class STATUS(Const):ERROR = (-3, '??')OK = (0, '??')
print STATUS.ERRORprint STATUS.static_values()print STATUS.STATIC_ATTRS
#Usage sample:user_input = 1#Validate input:print user_input in STATUS.static_values()#Template render like:print '<select>'for key, value in STATUS.STATIC_ATTRS.items():print '<option value="%s">%s</option>' % (value[0], value[1])print '</select>'

if __name__ == '__main__':main()

亚历克·托马斯简洁的回答的变体(支持获取枚举值的名称):

class EnumBase(type):def __init__(self, name, base, fields):super(EnumBase, self).__init__(name, base, fields)self.__mapping = dict((v, k) for k, v in fields.iteritems())def __getitem__(self, val):return self.__mapping[val]
def enum(*seq, **named):enums = dict(zip(seq, range(len(seq))), **named)return EnumBase('Enum', (), enums)
Numbers = enum(ONE=1, TWO=2, THREE='three')print Numbers.TWOprint Numbers[Numbers.ONE]print Numbers[2]print Numbers['three']

我喜欢使用列表或集合作为枚举。例如:

>>> packet_types = ['INIT', 'FINI', 'RECV', 'SEND']>>> packet_types.index('INIT')0>>> packet_types.index('FINI')1>>>

我通常使用的解决方案是这个简单的函数来获取动态创建的类的实例。

def enum(names):"Create a simple enumeration having similarities to C."return type('enum', (), dict(map(reversed, enumerate(names.replace(',', ' ').split())), __slots__=()))()

使用它就像使用具有要引用的名称的字符串调用函数一样简单。

grade = enum('A B C D F')state = enum('awake, sleeping, dead')

这些值只是整数,因此如果需要,您可以利用它(就像在C语言中一样)。

>>> grade.A0>>> grade.B1>>> grade.F == 4True>>> state.dead == 2True

Python 2.7和find_name()

这是一个易于阅读的实现所选择的想法与一些辅助方法,这可能是比“reverse_mapping”更Pythonic和清洁使用。

为了解决下面的一些注释,枚举对于防止代码中的拼写错误非常有用,例如对于状态机、错误分类器等。

def Enum(*sequential, **named):"""Generate a new enum type. Usage example:
ErrorClass = Enum('STOP','GO')print ErrorClass.find_name(ErrorClass.STOP)= "STOP"print ErrorClass.find_val("STOP")= 0ErrorClass.FOO     # Raises AttributeError"""enums = { v:k for k,v in enumerate(sequential) } if not named else named
@classmethoddef find_name(cls, val):result = [ k for k,v in cls.__dict__.iteritems() if v == val ]if not len(result):raise ValueError("Value %s not found in Enum" % val)return result[0]
@classmethoddef find_val(cls, n):return getattr(cls, n)
enums['find_val'] = find_valenums['find_name'] = find_namereturn type('Enum', (), enums)

虽然最初的枚举提议PEP 354在几年前被拒绝了,但它不断回来。某种枚举本打算添加到3.2中,但它被推回到3.3,然后被遗忘了。现在有一个PEP 435打算包含在Python 3.4中。PEP 435的参考实现是#0

截至2013年4月,似乎有一个普遍的共识,即东西应该添加到3.4的标准库中——只要人们能够就那个“东西”应该是什么达成一致。这是困难的部分。参见从这里这里开始的线程,以及2013年初几个月的其他六个线程。

同时,每次出现这个问题时,PyPI、ActiveState等都会出现一系列新的设计和实现,所以如果你不喜欢FLUFL设计,请尝试pypi搜索

在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.redColor.greenColor.blue

编程访问:

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

有关更多信息,请参阅该提案。官方留档可能很快就会跟进。

Python中的标准是PEP 435,因此在Python 3.4+中可以使用Enum类:

>>> from enum import Enum>>> class Colors(Enum):...     red = 1...     green = 2...     blue = 3>>> for color in Colors: print colorColors.redColors.greenColors.blue

以下是一种我认为有价值的具有一些不同特征的方法:

  • 允许基于枚举中的顺序进行>和<比较,而不是词法顺序
  • 可以按名称、属性或索引对项目进行寻址:x. a、x['a']或x[0]
  • 支持切片操作,如[:]或[-1]

最重要的是防止不同类型的枚举之间的比较

基于http://code.activestate.com/recipes/413486-first-class-enums-in-python

这里包含了许多文档测试来说明这种方法的不同之处。

def enum(*names):"""SYNOPSISWell-behaved enumerated type, easier than creating custom classes
DESCRIPTIONCreate a custom type that implements an enumeration.  Similar in conceptto a C enum but with some additional capabilities and protections.  Seehttp://code.activestate.com/recipes/413486-first-class-enums-in-python/.
PARAMETERSnames       Ordered list of names.  The order in which names are givenwill be the sort order in the enum type.  Duplicate namesare not allowed.  Unicode names are mapped to ASCII.
RETURNSObject of type enum, with the input names and the enumerated values.
EXAMPLES>>> letters = enum('a','e','i','o','u','b','c','y','z')>>> letters.a < letters.eTrue
## index by property>>> letters.aa
## index by position>>> letters[0]a
## index by name, helpful for bridging string inputs to enum>>> letters['a']a
## sorting by order in the enum() create, not character value>>> letters.u < letters.bTrue
## normal slicing operations available>>> letters[-1]z
## error since there are not 100 items in enum>>> letters[99]Traceback (most recent call last):...IndexError: tuple index out of range
## error since name does not exist in enum>>> letters['ggg']Traceback (most recent call last):...ValueError: tuple.index(x): x not in tuple
## enums must be named using valid Python identifiers>>> numbers = enum(1,2,3,4)Traceback (most recent call last):...AssertionError: Enum values must be string or unicode
>>> a = enum('-a','-b')Traceback (most recent call last):...TypeError: Error when calling the metaclass bases__slots__ must be identifiers
## create another enum>>> tags = enum('a','b','c')>>> tags.aa>>> letters.aa
## can't compare values from different enums>>> letters.a == tags.aTraceback (most recent call last):...AssertionError: Only values from the same enum are comparable
>>> letters.a < tags.aTraceback (most recent call last):...AssertionError: Only values from the same enum are comparable
## can't update enum after create>>> letters.a = 'x'Traceback (most recent call last):...AttributeError: 'EnumClass' object attribute 'a' is read-only
## can't update enum after create>>> del letters.uTraceback (most recent call last):...AttributeError: 'EnumClass' object attribute 'u' is read-only
## can't have non-unique enum values>>> x = enum('a','b','c','a')Traceback (most recent call last):...AssertionError: Enums must not repeat values
## can't have zero enum values>>> x = enum()Traceback (most recent call last):...AssertionError: Empty enums are not supported
## can't have enum values that look like special function names## since these could collide and lead to non-obvious errors>>> x = enum('a','b','c','__cmp__')Traceback (most recent call last):...AssertionError: Enum values beginning with __ are not supported
LIMITATIONSEnum values of unicode type are not preserved, mapped to ASCII instead.
"""## must have at least one enum valueassert names, 'Empty enums are not supported'## enum values must be stringsassert len([i for i in names if not isinstance(i, types.StringTypes) and not \isinstance(i, unicode)]) == 0, 'Enum values must be string or unicode'## enum values must not collide with special function namesassert len([i for i in names if i.startswith("__")]) == 0,\'Enum values beginning with __ are not supported'## each enum value must be unique from all othersassert names == uniquify(names), 'Enums must not repeat values'
class EnumClass(object):""" See parent function for explanation """
__slots__ = names
def __iter__(self):return iter(constants)
def __len__(self):return len(constants)
def __getitem__(self, i):## this makes xx['name'] possibleif isinstance(i, types.StringTypes):i = names.index(i)## handles the more normal xx[0]return constants[i]
def __repr__(self):return 'enum' + str(names)
def __str__(self):return 'enum ' + str(constants)
def index(self, i):return names.index(i)
class EnumValue(object):""" See parent function for explanation """
__slots__ = ('__value')
def __init__(self, value):self.__value = value
value = property(lambda self: self.__value)
enumtype = property(lambda self: enumtype)
def __hash__(self):return hash(self.__value)
def __cmp__(self, other):assert self.enumtype is other.enumtype, 'Only values from the same enum are comparable'return cmp(self.value, other.value)
def __invert__(self):return constants[maximum - self.value]
def __nonzero__(self):## return bool(self.value)## Original code led to bool(x[0])==False, not correctreturn True
def __repr__(self):return str(names[self.value])
maximum = len(names) - 1constants = [None] * len(names)for i, each in enumerate(names):val = EnumValue(i)setattr(EnumClass, each, val)constants[i] = valconstants = tuple(constants)enumtype = EnumClass()return enumtype

在答案列表中没有看到这个,这是我做的一个。它允许使用'in'关键字和len()方法:

class EnumTypeError(TypeError):pass
class Enum(object):"""Minics enum type from different languagesUsage:Letters = Enum(list('abc'))a = Letters.aprint(a in Letters) # Trueprint(54 in Letters) # False"""def __init__(self, enums):if isinstance(enums, dict):self.__dict__.update(enums)elif isinstance(enums, list) or isinstance(enums, tuple):self.__dict__.update(**dict((v,k) for k,v in enumerate(enums)))else:raise EnumTypeError
def __contains__(self, key):return key in self.__dict__.values()
def __len__(self):return len(self.__dict__.values())

if __name__ == '__main__':print('Using a dictionary to create Enum:')Letters = Enum(dict((v,k) for k,v in enumerate(list('abcde'))))a = Letters.aprint('\tIs a in e?', a in Letters)print('\tIs 54 in e?', 54 in Letters)print('\tLength of Letters enum:', len(Letters))
print('\nUsing a list to create Enum:')Letters = Enum(list('abcde'))a = Letters.aprint('\tIs a in e?', a in Letters)print('\tIs 54 in e?', 54 in Letters)print('\tLength of Letters enum:', len(Letters))
try:# make sure we raise an exception if we pass an invalid argFailure = Enum('This is a Failure')print('Failure')except EnumTypeError:print('Success!')

输出:

Using a dictionary to create Enum:Is a in e? TrueIs 54 in e? FalseLength of Letters enum: 5
Using a list to create Enum:Is a in e? TrueIs 54 in e? FalseLength of Letters enum: 5Success!

从Python 3.4开始,官方支持枚举。您可以找到留档和示例在Python 3.4留档页面

枚举是使用类语法创建的,这使得它们很容易读写。另一种创建方法在函数式API。要定义枚举,子类Enum如下:

from enum import Enumclass Color(Enum):red = 1green = 2blue = 3

这是我在这里找到的一个不错的Python食谱:http://code.activestate.com/recipes/577024-yet-another-enum-for-python/

def enum(typename, field_names):"Create a new enumeration type"
if isinstance(field_names, str):field_names = field_names.replace(',', ' ').split()d = dict((reversed(nv) for nv in enumerate(field_names)), __slots__ = ())return type(typename, (object,), d)()

示例用法:

STATE = enum('STATE', 'GET_QUIZ, GET_VERSE, TEACH')

更多细节可以在食谱页面上找到。

保持简单,使用旧的Python 2. x(参见下面的Python 3!):

class Enum(object):def __init__(self, tupleList):self.tupleList = tupleList    
def __getattr__(self, name):return self.tupleList.index(name)

然后:

DIRECTION = Enum(('UP', 'DOWN', 'LEFT', 'RIGHT'))DIRECTION.DOWN1

使用python3时保持简单:

from enum import Enumclass MyEnum(Enum):UP = 1DOWN = 2LEFT = 3RIGHT = 4

然后:

MyEnum.DOWN

见:https://docs.python.org/3/library/enum.html

对于旧的Python 2. x

def enum(*sequential, **named):enums = dict(zip(sequential, [object() for _ in range(len(sequential))]), **named)return type('Enum', (), enums)

如果你命名它,是你的问题,但如果不创建对象而不是值允许你这样做:

>>> DOG = enum('BARK', 'WALK', 'SIT')>>> CAT = enum('MEOW', 'WALK', 'SIT')>>> DOG.WALK == CAT.WALKFalse

当使用此处的其他实现时(也在我的示例中使用命名实例时),您必须确保永远不要尝试比较来自不同枚举的对象。因为这是一个可能的陷阱:

>>> DOG = enum('BARK'=1, 'WALK'=2, 'SIT'=3)>>> CAT = enum('WALK'=1, 'SIT'=2)>>> pet1_state = DOG.BARK>>> pet2_state = CAT.WALK>>> pet1_state == pet2_stateTrue

哎呀!