Я в основном разработчик С#, но сейчас я работаю над проектом на Python.
Как я могу представить эквивалент Enum в Python?
Я в основном разработчик С#, но сейчас я работаю над проектом на Python.
Как я могу представить эквивалент Enum в Python?
Перечисления были добавлены в Python 3.4, как описано в PEP 435. Он также был backported до 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 и 2.4 на pypi.
Для более продвинутых методов Enum попробуйте aenum library (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. Например, члены перечисления отличаются от целых чисел и состоят из name
и value
.
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)
Использование range
также позволяет установить любое начальное значение:
Pen, Pencil, Eraser = range(9, 12)
В дополнение к вышесказанному, если вы также требуете, чтобы элементы принадлежали некоторому контейнеру, затем вставляйте их в класс:
class Stationery:
Pen, Pencil, Eraser = range(0, 3)
Чтобы использовать элемент перечисления, теперь вам нужно будет использовать имя контейнера и имя элемента:
stype = Stationery.Pen
Сложное перечисление:
Для длинных списков перечисления или более сложных применений перечисления этих решений не хватит. Вы можете посмотреть рецепт Will Ware для имитации перечислений в Python, опубликованный в Python Cookbook. Онлайн-версия этой версии здесь.
Дополнительная информация:
PEP 354: Перечисления на Python содержат интересные детали предложения для перечисления на Python и почему он был отклонен.
Типовой шаблон перечисления, который использовался в Java pre-JDK 5, имеет количество преимуществ. Как и в Alexandru, вы создаете поля класса и класса являются значениями перечисления; однако перечисление значения являются экземплярами класса, а не малыми целыми числами. Это преимущество в том, что ваши значения перечисления не случайно сравнивают равные к малым целым, вы можете контролировать, как они печатаются, добавлять произвольные методы, если это полезно и сделать утверждения с использованием isststance:
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, в том числе:
Класс 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).
Однако в ситуациях, когда в C будет вызываться enum
, я обычно заканчиваю просто используя простые строки: из-за способа реализации объектов/атрибутов (C) Python оптимизированный для работы очень быстро с короткими строками, так что на самом деле не было бы никакой выгоды от использования целых чисел. Для защиты от опечаток/недопустимых значений вы можете вставлять проверки в выбранных местах.
ANIMALS = ['cat', 'dog', 'python']
def take_for_a_walk(animal):
assert animal in ANIMALS
...
(Один недостаток по сравнению с использованием класса заключается в том, что вы теряете преимущество автозаполнения)
В 2013-05-10 Гвидо согласился принять 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 - исключение во время выполнения).
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)
Хммм... Полагаю, что самое близкое к перечислению будет словарем, определенным так:
months = {
'January': 1,
'February': 2,
...
}
или
months = dict(
January=1,
February=2,
...
)
Затем вы можете использовать символическое имя для таких констант:
mymonth = months['January']
Существуют и другие параметры, такие как список кортежей или кортеж кортежей, но словарь является единственным, который предоставляет вам "символическую" (постоянную строку) способ доступа к значение.
Редактировать: мне нравится ответить Александру тоже!
Другая, очень простая реализация переименования в 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())
Как и метод выше этого подкласса set
, это позволяет:
'FOO' in MyEnum
other = MyEnum.FOO
assert other == MyEnum.FOO
Но имеет большую гибкость, поскольку он может иметь разные ключи и значения. Это позволяет
MyEnum.FOO < MyEnum.BAR
действовать так, как ожидается, если вы используете версию, которая заполняет порядковые номера.
Что я использую:
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 3.4 будет официальная поддержка перечислений. Вы можете найти документацию и примеры здесь, на странице документации Python 3.4.
Перечисления создаются с использованием синтаксиса класса, что упрощает их читать и писать. Альтернативный метод создания описан в Функциональный API. Чтобы определить перечисление, подкласс Enum выглядит следующим образом:
from enum import Enum
class Color(Enum):
red = 1
green = 2
blue = 3
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)
Держите его простым:
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
Мне приходилось иметь дело с классом 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()
все производят наиболее полезный вывод, соответственно, имя enumartion, его целочисленное значение и выражение Python, которое оценивается обратно в перечисление.is
Мне действительно нравится решение Алека Томаса (http://stackoverflow.com/a/1695250):
def enum(**enums):
'''simple constant "enums"'''
return type('Enum', (object,), enums)
Это элегантный и чистый вид, но это просто функция, которая создает класс с указанными атрибутами.
С небольшой модификацией функции мы можем заставить ее действовать немного "enumy":
ПРИМЕЧАНИЕ. Я создал следующие примеры, пытаясь воспроизвести поведение pygtk нового типа 'enums' (например, 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'
Новый стандарт в Python - PEP 435, поэтому класс Enum будет доступен в следующих версиях Python:
>>> from enum import Enum
Однако, чтобы начать использовать его сейчас, вы можете установить оригинальную библиотеку, которая мотивировала PEP:
$ pip install flufl.enum
Тогда вы можете использовать его в соответствии с его онлайн-руководством:
>>> from flufl.enum import Enum
>>> class Colors(Enum):
... red = 1
... green = 2
... blue = 3
>>> for color in Colors: print color
Colors.red
Colors.green
Colors.blue
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
Хлоп!
Пакет enum от 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.red
True
>>> shirt_color.index
2
>>> str(shirt_color)
'green'
Предложение Александру об использовании констант класса для перечислений работает достаточно хорошо.
Я также хотел бы добавить словарь для каждого набора констант для поиска удобочитаемого строкового представления.
Это служит двум целям: а) он предоставляет простой способ для правильной печати вашего перечисления и 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())
Здесь подход с некоторыми другими характеристиками я считаю ценным:
и самое главное предотвращает сравнения между перечислениями разных типов !
Основано на http://code.activestate.com/recipes/413486-first-class-enums-in-python.
Многие документы включены сюда, чтобы проиллюстрировать, в чем отличие этого подхода.
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
Вот вариант на решение 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
Это решение - простой способ получить класс для перечисления, определенного как список (не более раздражающие целые назначения):
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',
))
Пока первоначальное предложение enum, PEP 354, было отклонено несколько лет назад, оно продолжает возвращаться. Какой-то перечислитель должен был быть добавлен к 3.2, но его отбросили до 3.3 и затем забыли. И теперь есть PEP 435, предназначенный для включения в Python 3.4. Эталонная реализация PEP 435 flufl.enum
.
По состоянию на апрель 2013 года, похоже, существует общее мнение о том, что что-то должно быть добавлено в стандартную библиотеку в 3.4 - до тех пор, пока люди могут договориться о том, что это "что-то" должно быть. Это тяжелая часть. Смотрите темы, начинающиеся с здесь и здесь, и полдюжины другие темы в первые месяцы 2013 года.
Между тем, каждый раз, когда это появляется, множество новых проектов и реализаций появляется на PyPI, ActiveState и т.д., поэтому, если вам не нравится дизайн FLUFL, попробуйте Поиск PyPI.
Вариант (с поддержкой получения имени значения перечисления) в Алека Томаса аккуратный ответ:
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']