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

我如何在Python中表示等价的Enum ?


当前回答

我使用元类来实现枚举(在我的想法中,它是一个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 types
            attrs = {}
            val_set = set()
            #Maybe more
            filter_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] = value
            return attrs, val_set

        attrs, val_set = static_attrs()
        #Set STATIC_ATTRS to class instance so that can reuse
        setattr(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 1
    2) 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.ERROR
    print 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()

其他回答

Alec Thomas简洁回答的一个变体(支持获取枚举值的名称):

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.TWO
print Numbers[Numbers.ONE]
print Numbers[2]
print Numbers['three']

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 color
Colors.red
Colors.green
Colors.blue

为什么枚举必须是整数?不幸的是,在不改变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

使用下面的方法。

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化。它不是一个真正的Enum,但它完成了这项工作。

枚举已按照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'

如果你使用MyPy,另一种表达“枚举”的方式是typing.Literal。

例如:

from typing import Literal #python >=3.8
from 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') # error
hello_animal('bee') # passes