我主要是一名 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'
如果您使用 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
在 PEP 435 之前,Python 没有等价物,但您可以实现自己的。
我自己,我喜欢保持简单(我在网上看到了一些非常复杂的例子),像这样......
class Animal:
DOG = 1
CAT = 2
x = Animal.DOG
在 Python 3.4 ( PEP 435 ) 中,您可以使Enum成为基类。这为您提供了一些额外的功能,如 PEP 中所述。例如,枚举成员不同于整数,它们由 aname
和 a组成value
。
from enum import Enum
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
列表,Mark Harrison (上图)的解决方案非常棒:
Pen, Pencil, Eraser = range(0, 3)
使用 arange
还允许您设置任何起始值:
Pen, Pencil, Eraser = range(9, 12)
除了上述之外,如果您还要求项目属于某种容器,则将它们嵌入到一个类中:
class Stationery:
Pen, Pencil, Eraser = range(0, 3)
要使用枚举项,您现在需要使用容器名称和项名称:
stype = Stationery.Pen
复杂枚举:
对于枚举的长列表或更复杂的枚举使用,这些解决方案是不够的。您可以查看Python Cookbook中发布的 Will Ware for Simulating Enumerations in Python的配方。此处提供了该版本的在线版本。
更多信息:
PEP 354: Enumerations in Python有关于 Python 中枚举提案的有趣细节以及它被拒绝的原因。
Java pre-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 上最近的一个帖子指出有几个枚举库在野外,包括:
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)]
所以,我同意。让我们不要在 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 食谱中的顶级版本感兴趣)。
但是,enum
在 C 中调用 an 的情况下,我通常最终只使用简单的字符串:由于对象/属性的实现方式,(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
有关详细信息,请参阅提案。官方文档可能很快就会出现。
我更喜欢像这样在 Python 中定义枚举:
class Animal:
class Dog: pass
class Cat: pass
x = Animal.Dog
它比使用整数更能防止错误,因为你不必担心确保整数是唯一的(例如,如果你说 Dog = 1 和 Cat = 1,你会被搞砸的)。
它比使用字符串更能防止错误,因为您不必担心拼写错误(例如 x == "catt" 会静默失败,但 x == Animal.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): pass
class Cat(Symbol): pass
然后,如果您使用这些值来索引字典,请求它的表示将使它们看起来很好:
>>> mydict = {Animal.Dog: 'Wan Wan', Animal.Cat: 'Nyaa'}
>>> mydict
{Animal.Dog: 'Wan Wan', Animal.Cat: 'Nyaa'}
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)
从 Python 3.4 开始,官方支持枚举。您可以在 Python 3.4 文档页面上找到文档和示例。
枚举是使用类语法创建的,这使得它们易于阅读和编写。功能 API 中描述了另一种创建方法。要定义一个枚举,子类 Enum 如下:
from enum import Enum
class Color(Enum):
red = 1
green = 2
blue = 3
另一个非常简单的 Python 枚举实现,使用namedtuple
:
from collections import namedtuple
def enum(*keys):
return namedtuple('Enum', keys)(*keys)
MyEnum = enum('FOO', 'BAR', 'BAZ')
或者,或者,
# With sequential number values
def enum(*keys):
return namedtuple('Enum', keys)(*range(len(keys)))
# From a dict / keyword args
def 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'
就像上面那个 subclasses 的方法一样set
,这允许:
'FOO' in MyEnum
other = MyEnum.FOO
assert other == MyEnum.FOO
但具有更大的灵活性,因为它可以有不同的键和值。这允许
MyEnum.FOO < MyEnum.BAR
如果您使用填充连续数字值的版本,则按预期运行。
嗯......我想最接近枚举的东西是字典,定义如下:
months = {
'January': 1,
'February': 2,
...
}
或者
months = dict(
January=1,
February=2,
...
)
然后,您可以对常量使用符号名称,如下所示:
mymonth = months['January']
还有其他选项,例如元组列表或元组元组,但字典是唯一为您提供“符号”(常量字符串)方式来访问值的选项。
编辑:我也喜欢 Alexandru 的回答!
我用什么:
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 模型中用作选择的二元组。
保持简单,使用旧的 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.DOWN
1
使用Python 3时保持简单:
from enum import Enum
class MyEnum(Enum):
UP = 1
DOWN = 2
LEFT = 3
RIGHT = 4
然后:
MyEnum.DOWN
Python 中的标准是PEP 435,因此 Enum 类在 Python 3.4+ 中可用:
>>> 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
davidg 建议使用 dicts。我会更进一步并使用集合:
months = set('January', 'February', ..., 'December')
现在您可以测试一个值是否与集合中的一个值匹配,如下所示:
if m in months:
不过,像 dF 一样,我通常只使用字符串常量来代替枚举。
这是我见过的最好的:“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)
为了解码二进制文件格式,我有机会需要一个 Enum 类。我碰巧想要的特性是简洁的枚举定义,通过整数值或字符串自由创建枚举实例的能力,以及有用的repr
esentation。这就是我最终得到的结果:
>>> 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.Lemon
Citrus.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.Apple
Fruit.Apple
>>> Fruit.Lemon
Citrus.Lemon
>>> Fruit(1)
Citrus.Lemon
>>> Fruit(3)
Fruit.Apple
>>> "%d %s %r" % ((Fruit.Apple,)*3)
'3 Apple Fruit.Apple'
>>> Fruit(1) is Citrus.Lemon
True
主要特点:
str()
,int()
并且repr()
都可能产生最有用的输出,分别是枚举的名称、它的整数值和返回枚举的 Python 表达式。is
对于旧的 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.WALK
False
当使用此处的其他实现时(在我的示例中也使用命名实例时),您必须确保永远不要尝试比较来自不同枚举的对象。因为这里有一个可能的陷阱:
>>> 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_state
True
哎呀!
我真的很喜欢 Alec Thomas 的解决方案 (http://stackoverflow.com/a/1695250):
def enum(**enums):
'''simple constant "enums"'''
return type('Enum', (object,), enums)
它看起来优雅而干净,但它只是一个创建具有指定属性的类的函数。
通过对函数稍加修改,我们可以让它表现得更“枚举”:
注意:我通过尝试重现 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
这将创建一个基于指定类型的枚举。除了像前面的函数一样提供属性访问之外,它的行为与您期望的 Enum 就类型相关的行为一样。它还继承了基类。
例如,整数枚举:
>>> Numbers = enum_base(int, ONE=1, TWO=2, THREE=3)
>>> Numbers.ONE
1
>>> x = Numbers.TWO
>>> 10 + x
12
>>> 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 = key
setattr(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'
PyPI的 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'
这是一种我认为有价值的具有一些不同特征的方法:
最重要的是防止不同类型的枚举之间的比较!
密切基于http://code.activestate.com/recipes/413486-first-class-enums-in-python。
这里包含许多 doctests 来说明这种方法的不同之处。
def enum(*names):
"""
SYNOPSIS
Well-behaved enumerated type, easier than creating custom classes
DESCRIPTION
Create a custom type that implements an enumeration. Similar in concept
to a C enum but with some additional capabilities and protections. See
http://code.activestate.com/recipes/413486-first-class-enums-in-python/.
PARAMETERS
names Ordered list of names. The order in which names are given
will be the sort order in the enum type. Duplicate names
are not allowed. Unicode names are mapped to ASCII.
RETURNS
Object 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.e
True
## index by property
>>> letters.a
a
## 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.b
True
## 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.a
a
>>> letters.a
a
## can't compare values from different enums
>>> letters.a == tags.a
Traceback (most recent call last):
...
AssertionError: Only values from the same enum are comparable
>>> letters.a < tags.a
Traceback (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.u
Traceback (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
LIMITATIONS
Enum values of unicode type are not preserved, mapped to ASCII instead.
"""
## must have at least one enum value
assert names, 'Empty enums are not supported'
## enum values must be strings
assert 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 names
assert 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 others
assert 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'] possible
if 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 correct
return True
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
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())
使用以下内容。
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。它不是真正的枚举,但它可以完成工作。
虽然最初的枚举提案PEP 354几年前被拒绝了,但它不断出现。某种枚举本来打算添加到 3.2,但它被推回 3.3 然后被遗忘了。现在有一个PEP 435旨在包含在 Python 3.4 中。PEP 435 的参考实现是flufl.enum
.
截至 2013 年 4 月,似乎普遍认为应该在 3.4 的标准库中添加一些东西——只要人们能就“东西”应该是什么达成一致。那是困难的部分。查看从此处和此处开始的线程,以及 2013 年头几个月的其他六个线程。
同时,每次出现这种情况时,PyPI、ActiveState 等都会出现大量新的设计和实现,所以如果你不喜欢 FLUFL 设计,请尝试PyPI 搜索。
在答案列表中没有看到这个,这是我掀起的一个。它允许使用 'in' 关键字和 len() 方法:
class EnumTypeError(TypeError):
pass
class Enum(object):
"""
Minics enum type from different languages
Usage:
Letters = Enum(list('abc'))
a = Letters.a
print(a in Letters) # True
print(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.a
print('\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.a
print('\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 arg
Failure = Enum('This is a Failure')
print('Failure')
except EnumTypeError:
print('Success!')
输出:
Using a dictionary to create Enum:
Is a in e? True
Is 54 in e? False
Length of Letters enum: 5
Using a list to create Enum:
Is a in e? True
Is 54 in e? False
Length of Letters enum: 5
Success!
这是我在这里找到的一个不错的 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')
更多细节可以在食谱页面上找到。
有趣的是,前几天我刚需要这个,但我找不到值得使用的实现......所以我写了自己的:
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))
这是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 == 0
assert x.TIGGER == 1
此解决方案是获取定义为列表的枚举的类的简单方法(不再烦人的整数分配):
枚举.py:
import new
def create(class_name, names):
return new.classobj(
class_name, (object,), dict((y, x) for x, y in enumerate(names))
)
例子.py:
import enumeration
Colors = enumeration.create('Colors', (
'red',
'orange',
'yellow',
'green',
'blue',
'violet',
))
我喜欢使用列表或集合作为枚举。例如:
>>> packet_types = ['INIT', 'FINI', 'RECV', 'SEND']
>>> packet_types.index('INIT')
0
>>> packet_types.index('FINI')
1
>>>
我在 pyparsing 中需要一些符号常量来表示二元运算符的左右关联性。我使用了这样的类常量:
# 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类别。
继 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 + ">"
为什么枚举必须是整数?不幸的是,在不改变 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
我喜欢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
所有类变量都定义为一个元组,就像构造函数一样。到目前为止,您不能使用命名参数。
我使用元类来实现枚举(在我看来,它是一个常量)。这是代码:
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']
这是所选想法的易于阅读的实现,带有一些辅助方法,这些方法可能比“reverse_mapping”更Pythonic和更简洁。需要 Python >= 2.7。
为了解决下面的一些评论,枚举对于防止代码中的拼写错误非常有用,例如对于状态机、错误分类器等。
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")
= 0
ErrorClass.FOO # Raises AttributeError
"""
enums = { v:k for k,v in enumerate(sequential) } if not named else named
@classmethod
def 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]
@classmethod
def find_val(cls, n):
return getattr(cls, n)
enums['find_val'] = find_val
enums['find_name'] = find_name
return type('Enum', (), enums)
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
我通常使用的解决方案是这个简单的函数来获取动态创建的类的实例。
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.A
0
>>> grade.B
1
>>> grade.F == 4
True
>>> state.dead == 2
True