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

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

我怎样才能代表 Python 中的 Enum?

答案

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

对于更高级的枚举技术尝试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'

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

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

class Animal:
    DOG = 1
    CAT = 2

x = Animal.DOG

在 Python 3.4( PEP 435 )中,您可以将Enum 设为基类。这会给您带来一些额外的功能,如 PEP 中所述。例如,枚举成员不同于整数,它们由namevalue

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

这是一个实现:

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

这是它的用法:

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

print(Animals.DOG)

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

dog, cat, rabbit = range(3)

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

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

最佳的解决方案取决于您从 enum要求。

简单枚举:

如果您只需要enum来标识不同名称列表,那么马克 · 哈里森 (上述)的解决方案就很棒:

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

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

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

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

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

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

stype = Stationery.Pen

复合枚举:

对于一长串的枚举或更复杂的枚举使用,这些解决方案将无法满足要求。您可以参考《 Will Ware 在Python Cookbook 中发布的Python 模拟枚举 》中的食谱 。该版本的在线版本可在此处获得

更多信息:

PEP 354:Python 枚举中有一个有趣的细节,建议使用 Python 枚举,以及为什么拒绝该枚举。

Java 之前的 JDK 5 中使用的类型安全枚举模式具有许多优点。就像在 Alexandru 的答案中一样,您创建了一个类,并且类级别字段是枚举值。但是,枚举值是类的实例,而不是小整数。这样做的优点是您的枚举值不会无意间等于小整数,您可以控制它们的打印方式,添加有用的任意方法,并使用 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 上的一个最新线程指出,野外有几个枚举库,包括:

枚举类可以是单行。

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)]

所以,我同意。让我们不要在 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 没有等效于enum的内置函数,其他答案也有实现自己的想法(您可能也对 Python 食谱中的顶级版本感兴趣)。

但是,在需要用 C 调用enum情况下,我通常最终只使用简单的字符串 :由于对象 / 属性的实现方式,(C)Python 进行了优化,无论如何都可以非常快速地使用短字符串,因此使用整数实际上不会对性能产生任何好处。为了防止输入错误 / 无效值,您可以在所选位置插入支票。

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

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

(与使用类相比,一个缺点是您失去了自动完成功能的优势)

在 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

有关更多信息,请参阅建议 。官方文档可能很快就会发布。