如何在Python中表示"枚举"?

时间:2020-03-05 18:45:41  来源:igfitidea点击:

我主要是Cdeveloper,但是我目前正在使用Python进行项目。

如何在Python中表示等效的枚举?

解决方案

回答

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

我自己,我喜欢保持简单(我在网上看到了一些非常复杂的示例),类似这样的东西...

class Animal:
    DOG = 1
    CAT = 2

x = Animal.DOG

在Python 3.4(PEP 435)中,我们可以将Enum设为基类。这会给我们带来一些额外的功能,如PEP中所述。例如,枚举成员不同于整数,并且由"名称"和"值"组成。

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

回答

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

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

或者

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

然后,可以为常量使用符号名称,如下所示:

mymonth = months['January']

还有其他选项,例如元组列表或者元组的元组,但是该词典是唯一为我们提供"符号"(常量字符串)访问方式的方法。
价值。

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

回答

Python没有等效于enum的内置函数,其他答案也有实现自己的想法(我们可能也对Python食谱中的顶级版本感兴趣)。

但是,在需要用C调用"枚举"的情况下,我通常最终只会使用简单的字符串:由于对象/属性的实现方式,(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)

回答

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上的最新线程指出,野外有几个枚举库,包括:

  • 氟虫
  • lazr.enum
  • ...以及富有想象力的枚举

回答

大卫建议使用字典。我将更进一步并使用集合:

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

现在,我们可以像这样测试一个值是否与集合中的值之一匹配:

if m in months:

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

回答

Alexandru关于将类常量用于枚举的建议非常有效。

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

这有两个目的:a)提供一种简单的方式来枚举枚举,b)字典在逻辑上将常量分组,以便我们可以测试成员资格。

class Animal:    
  TYPE_DOG = 1
  TYPE_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] = v
        return 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-only
            raise NotImplementedError
    return Foo()

像这样使用它:

Animal = enum('DOG', 'CAT')
Animal.DOG # returns 0
Animal.CAT # returns 1
Animal.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=value
        self.__name=name
        self.Type=type
    def __str__(self):
        return self.__name
    def __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 self
        elif 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 self
        elif 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 True
        return 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))

    @property
    def Name(self):
        return self.__name

    @property
    def Value(self):
        return self.__value

class EnumMeta(type):
    @staticmethod
    def __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 append
                rev[value]=(True,items+newKeys)
            elif not forced:#move it to a new spot
                next=nextIter.next()
                EnumMeta.__addToReverseLookup(rev,next,items,nextIter,False)
                rev[value]=(force,newKeys)
            else: #not forcing this value
                next = nextIter.next()
                EnumMeta.__addToReverseLookup(rev,next,newKeys,nextIter,False)
                rev[value]=(force,newKeys)
        else:#set it and forget it
            rev[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 lookup
            for 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 lookup
            for value,fkeys in reverseLookup.iteritems():
                f,keys=fkeys
                for key in keys:
                    enum=EnumValue(key,value,cls)
                    setattr(cls,key,enum)
                    values[key]=enum
                reverseLookup[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 enum
    def 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):
    @staticmethod
    def __nextitr__():
        yield 0
        for 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.DRAFT
0
>>> state.RETRACTED
2
>>> state.FOO
Traceback (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模型中使用两个元组作为选择。

回答

最佳的解决方案取决于假枚举所需要的内容。

简单枚举:

如果我们只需要"枚举"作为标识不同项目的名称列表,那么马克·哈里森(上述)的解决方案就很棒:

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

使用range还可以设置任何起始值:

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

除上述内容外,如果我们还要求这些项目属于某种容器,则将它们嵌入一个类中:

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

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

stype = Stationery.Pen

复杂的枚举:

对于一长串的枚举或者更复杂的枚举使用,这些解决方案将无法满足要求。我们可以参考《 Python Cookbook》中发布的Will Ware的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模型选择,它看起来非常具有Python风格。它实际上不是一个枚举,但是可以完成工作。

回答

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

对于更高级的Enum技术,请尝试aenum库(2.7、3.3+,与" enum34"相同的作者。py2和py3之间的代码并不完全兼容,例如,在python 2中需要" order")。

  • 要使用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'

回答

在pypars中,我需要一些符号常量来表示二进制运算符的左右关联性。我使用了这样的类常量:

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

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

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

import opAssoc from pyparsing

这些枚举是唯一的,可以用" is"代替" =="进行测试,它们在我的代码中占用的资源很少,只需要一个较小的概念,并且可以轻松地导入到客户端代码中。它们不支持任何精美的str()行为,但到目前为止,它在YAGNI类别中。

回答

这是我见过的最好的一个:" Python中的一流枚举"

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

它给我们一个类,并且该类包含所有枚举。枚举可以相互比较,但是没有任何特殊的价值;我们不能将它们用作整数值。 (我之所以拒绝这样做,是因为我习惯于C枚举,它们是整数值。但是,如果我们不能将其用作整数,就不能将其错误地用作整数,因此总的来说,我认为这是一次胜利。 。)每个枚举都是一个唯一值。我们可以打印枚举,可以对其进行迭代,可以测试枚举值是否在该枚举中。它非常完整和光滑。

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

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

def Enum(*names):
   ##assert names, "Empty enums are not supported" # <- Don't like empty enums? Uncomment!

   class EnumClass(object):
      __slots__ = names
      def __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 = value
      Value = 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) < 0
      def __eq__(self, other):   return self.__cmp__(other) == 0
      def __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) - 1
   constants = [None] * len(names)
   for i, each in enumerate(names):
      val = EnumValue(i)
      setattr(EnumClass, each, val)
      constants[i] = val
   constants = 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.No
   print( 'Your answer is not', ~answer)

回答

这是一个实现:

class Enum(set):
    def __getattr__(self, name):
        if name in self:
            return name
        raise 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

    @classmethod
    def 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] = inst
        return inst

    @classmethod
    def 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的enum包提供了enum的可靠实现。较早的答案提到了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.red
True
>>> shirt_color.index
2
>>> str(shirt_color)
'green'

回答

为什么枚举必须是整数?不幸的是,在不更改Python语言的情况下,我想不出任何美观的结构来生成此代码,因此我将使用字符串:

class Enumerator(object):
    def __init__(self, name):
        self.name = name

    def __eq__(self, other):
        if self.name == other:
            return True
        return self is other

    def __ne__(self, other):
        if self.name != other:
            return False
        return 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.state
Out[4]: (Enumerator(standing), Enumerator(standing))

In [5]: c.state == c2.state
Out[5]: True

In [6]: c.State.mooing
Out[6]: Enumerator(mooing)

In [7]: c.State['mooing']
Out[7]: Enumerator(mooing)

In [8]: c.state = Cow.State.dead

In [9]: c.state == c2.state
Out[9]: False

In [10]: c.state == Cow.State.dead
Out[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():
        pass
    for 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=key
                instance._ordinal=ordinal
                setattr(self, key, instance)

        @classmethod
        def 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
@enum
class 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=name
        self.avalue=aparam

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

回答

所以,我同意。让我们不要在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]

回答

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

class Animal:
  class Dog: pass
  class Cat: pass

x = Animal.Dog

比起使用整数,它具有更好的防错功能,因为我们不必担心确保整数是唯一的(例如,如果我们说Dog = 1和Cat = 1将会被搞砸)。

与使用字符串相比,它更防错,因为我们不必担心拼写错误(例如
x ==" catt"静默失败,但是x == Animal.Catt是运行时异常)。