В основном я разработчик C #, но в настоящее время работаю над проектом на Python.
Как я могу представить эквивалент Enum в Python?
В основном я разработчик C #, но в настоящее время работаю над проектом на Python.
Как я могу представить эквивалент Enum в Python?
Enums были добавлены в Python 3.4, как описано в PEP 435. Он также был обратно перенесен в версии 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 и 2.4 на pypi.
Для более продвинутых методов Enum попробуйте библиотеку aenum (2.7, 3.3+, того же автора, что и enum34
. Code не полностью совместим между 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, другой способ выражения перечислений - 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
**named
) в функции перечисления для более старых версий должен поддерживать пользовательские значения: enum("blue", "red", "green", black=0)
- person Éric Araujo; 28.01.2020
До PEP 435 у Python не было эквивалента, но вы могли реализовать свой собственный.
Лично я предпочитаю, чтобы все было просто (я видел несколько ужасно сложных примеров в сети), что-то вроде этого ...
class Animal:
DOG = 1
CAT = 2
x = Animal.DOG
В Python 3.4 (PEP 435) вы можете сделать Enum базовый класс. Это дает вам немного дополнительных функций, описанных в PEP. Например, члены перечисления отличаются от целых чисел и состоят из name
и 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
object()
.
- person Tobu; 20.07.2010
X = object()
неудобно, потому что он не знает, что это такое (вы можете сравнивать только с namespace.X), и рискованно, потому что copy.deepcopy () или сериализация / десериализация создает новый, который не равен ни одному из тех, которые вы определенный! Числа, по крайней мере, безопасны, но строки обычно лучше.
- person Beni Cherniavsky-Paskin; 20.06.2012
Animal.CAT
является int
, и отображать ошибки типа при неправильном использовании; c) вспомогательные данные могут храниться вместе со значениями (например: MAMMALS = {CAT, DOG}
как другой атрибут класса).
- person Tyson; 01.03.2018
class Animal(Enum)
не будет работать из-за Cannot extend enumerations
- person dzieciou; 18.05.2020
Вот одна реализация:
class Enum(set):
def __getattr__(self, name):
if name in self:
return name
raise AttributeError
Вот его использование:
Animals = Enum(["DOG", "CAT", "HORSE"])
print(Animals.DOG)
__setattr__(self, name, value)
и, возможно, __delattr__(self, name)
, так что если вы случайно напишете Animals.DOG = CAT
, это не приведет к успеху.
- person Joonas Pulakka; 11.01.2011
Animals.DOG
; Кроме того, значения констант являются строками, так что сравнение с этими константами происходит медленнее, чем если бы, скажем, в качестве значений допускались целые числа.
- person Eric O Lebigot; 25.10.2011
setattr()
функцию внутри __init__()
метода вместо переопределения метода __getattr__()
. Я предполагаю, что это должно работать одинаково: class Enum (object): def __init __ (self, enum_string_list): if type (enum_string_list) == list: for enum_string в enum_string_list: setattr (self, enum_string, enum_string) else: raise AttributeError
- person Harshith J.V.; 15.11.2012
Animals.DOG
печатает СОБАКА, а не 0.
- person user2233706; 21.04.2014
try-except
?
- person bgusach; 30.01.2015
"My pet is a "+Animals.DOG
. Классу python3 Enum потребуется метод str для удаления имени класса и по-прежнему потребуется приведение str () ... если я чего-то не упускаю.
- person shao.lo; 23.07.2017
Если вам нужны числовые значения, вот самый быстрый способ:
dog, cat, rabbit = range(3)
В Python 3.x вы также можете добавить помеченный звездочкой заполнитель в конце, который поглотит все оставшиеся значения диапазона на случай, если вы не против тратить память и не можете считать:
dog, cat, rabbit, horse, *_ = range(100)
class Enum
и получить Enum.dog
и т. Д. Очень аккуратно. Не так хорошо, как Голанг, хотя.
- person Yuriy Pozniak; 09.05.2020
Лучшее решение для вас будет зависеть от того, что вам нужно от своей подделки 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
Сложное перечисление:
Для длинных списков enum или более сложных вариантов использования enum этих решений будет недостаточно. Вы можете посмотреть рецепт Уилла Уэра для моделирования перечислений в Python, опубликованный в Cookbook Python. Его онлайн-версия доступна здесь.
Дополнительная информация:
PEP 354: Enumerations in Python содержит интересные подробности предложения по enum в Python и почему оно было отклонено.
range
вы можете опустить первый аргумент, если он 0
- person ToonAlfrink; 13.06.2014
my_enum = dict(map(reversed, enumerate(str.split('Item0 Item1 Item2'))))
. Затем my_enum
можно использовать в поиске, например, my_enum['Item0']
может быть индексом в последовательности. Вы можете заключить результат str.split
в функцию, которая выдает исключение, если есть какие-либо дубликаты.
- person Ana Nimbus; 02.06.2018
Flag1, Flag2, Flag3 = [2**i for i in range(3)]
- person majkelx; 30.09.2019
Шаблон typeafe enum, который использовался в Java до JDK 5, имеет ряд преимуществ. Как и в ответе Александру, вы создаете класс, а поля уровня класса являются значениями перечисления; однако значения перечисления являются экземплярами класса, а не небольшими целыми числами. Это имеет то преимущество, что ваши значения enum случайно не сравниваются с маленькими целыми числами, вы можете контролировать, как они печатаются, добавлять произвольные методы, если это полезно, и делать утверждения, используя 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 в дикой природе, в том числе:
Класс 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)]
in
для поиска аккуратных членов. Пример использования: 'Claimed' in Enum(['Unclaimed', 'Claimed'])
- person Farzad Abdolhosseini; 09.07.2019
Итак, я согласен. Давайте не будем обеспечивать безопасность типов в 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]
Animal = Enum('horse', 'dog', 'cat')
. Я также ловлю ValueError в getattr в случае отсутствия элемента в self.values - кажется, лучше вместо этого вызвать AttributeError с предоставленной строкой имени. Я не смог заставить метакласс работать в Python 2.7 из-за моих ограниченных знаний в этой области, но мой собственный класс Enum отлично работает с прямыми методами экземпляра.
- person trojjer; 28.11.2013
Python не имеет встроенного эквивалента enum
, а в других ответах есть идеи для реализации вашего собственного (вас также может заинтересовать из верхней версии в кулинарной книге Python).
Однако в ситуациях, когда в C требуется enum
, я обычно заканчиваю простыми строками: из-за способа реализации объектов / атрибутов (C) Python в любом случае оптимизирован для очень быстрой работы с короткими строками, поэтому использование целых чисел не принесет никакого выигрыша в производительности. Чтобы защититься от опечаток / неверных значений, вы можете вставлять чеки в выбранные места.
ANIMALS = ['cat', 'dog', 'python']
def take_for_a_walk(animal):
assert animal in ANIMALS
...
(Один из недостатков по сравнению с использованием класса заключается в том, что вы теряете преимущество автозаполнения)
10 мая 2013 г. Гвидо согласился принять 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)
enum(names)
на enum(*names)
- тогда вы могли бы убрать лишнюю круглую скобку при его вызове.
- person Chris Lutz; 18.11.2009
Еще одна, очень простая реализация перечисления на 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'
Как и описанный выше метод с подклассами set
, это позволяет:
'FOO' in MyEnum
other = MyEnum.FOO
assert other == MyEnum.FOO
Но обладает большей гибкостью, поскольку может иметь разные ключи и значения. Это позволяет
MyEnum.FOO < MyEnum.BAR
действовать так, как ожидается, если вы используете версию, которая заполняет последовательные числовые значения.
collections
будет перемещен в collections.abc
в Python 3.10. Этот модуль, похоже, не включает namedtuple
, поэтому возможно, что namedtuple
останется внутри модуля collections
- person Luke Savefrogs; 22.07.2020
Начиная с Python 3.4 будет официальная поддержка перечислений. Вы можете найти документацию и примеры здесь, на странице документации Python 3.4.
Перечисления создаются с использованием синтаксиса класса, что упрощает их чтение и запись. Альтернативный метод создания описан в Функциональном API. Чтобы определить перечисление, создайте подкласс Enum следующим образом:
from enum import Enum
class Color(Enum):
red = 1
green = 2
blue = 3
Хммм ... Я полагаю, что ближе всего к перечислению будет словарь, определенный либо так:
months = {
'January': 1,
'February': 2,
...
}
or
months = dict(
January=1,
February=2,
...
)
Затем вы можете использовать символическое имя для констант, например:
mymonth = months['January']
Есть и другие варианты, такие как список кортежей или кортеж кортежей, но словарь - единственный, который предоставляет вам «символический» (постоянная строка) способ доступа к значению.
Изменить: мне тоже нравится ответ Александру!
Что использую:
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.
Будь проще:
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
Дэвидг рекомендует использовать dicts. Я бы пошел еще дальше и использовал наборы:
months = set('January', 'February', ..., 'December')
Теперь вы можете проверить, соответствует ли значение одному из значений в наборе следующим образом:
if m in months:
однако, как и dF, я обычно использую строковые константы вместо перечислений.
Новый стандарт в 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
Это лучшее, что я видел: "Перечисления первого класса в 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)
.__int__()
должен вызывать исключение для перечисления; но должен быть способ получить выгоду. И должна быть возможность устанавливать определенные целочисленные значения во время определения класса, чтобы вы могли использовать перечисление для таких вещей, как константы в модуле stat
.
- person steveha; 22.03.2012
Мне приходилось нуждаться в классе 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
__instancecheck__
метод. Классы не являются коллекциями экземпляров, поэтому 1 in Fruit
абсурд. Однако связанная версия поддерживает isinstance(1, Fruit)
, что было бы более правильным с точки зрения понятия классов и экземпляров.
- person SingleNegationElimination; 02.08.2012
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
Ой!
Мне очень нравится решение Алека Томаса (http://stackoverflow.com/a/1695250):
def enum(**enums):
'''simple constant "enums"'''
return type('Enum', (object,), enums)
Он элегантный и чистый, но это всего лишь функция, которая создает класс с указанными атрибутами.
Немного изменив функцию, мы можем заставить ее действовать немного более "enumy":
ПРИМЕЧАНИЕ. Я создал следующие примеры, пытаясь воспроизвести поведение перечислений нового стиля 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'
Пакет 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'
Вот подход с некоторыми характеристиками, которые я считаю ценными:
и самое главное предотвращает сравнение перечислений разных типов!
Основано на 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
Предложение Александру об использовании констант классов для перечислений работает довольно хорошо.
Мне также нравится добавлять словарь для каждого набора констант для поиска удобочитаемого строкового представления.
Это служит двум целям: а) предоставляет простой способ красиво напечатать ваше перечисление и б) словарь логически группирует константы, чтобы вы могли проверить членство.
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())
Хотя первоначальное предложение по перечислению, PEP 354, было отклонено много лет назад, оно сохраняет возвращаясь вверх. Некоторое перечисление должно было быть добавлено в 3.2, но оно было перенесено в 3.3, а затем забыто. А теперь есть PEP 435, предназначенный для включения в Python 3.4. Эталонной реализацией PEP 435 является flufl.enum
.
По состоянию на апрель 2013 года, похоже, существует общий консенсус в отношении того, что что-то следует добавить в стандартную библиотеку в 3.4 - при условии, что люди могут прийти к согласию о том, каким должно быть это "что-то". Это самая сложная часть. См. Цепочки, начинающиеся здесь и здесь и еще полдюжины других тем в первые месяцы 2013 года.
Между тем, каждый раз, когда это происходит, в PyPI, ActiveState и т. Д. Появляется множество новых проектов и реализаций, поэтому, если вам не нравится дизайн FLUFL, попробуйте PyPI search.
Используйте следующее.
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, и он выглядит очень питоническим. На самом деле это не Enum, но он выполняет свою работу.
Не видел этого в списке ответов, вот тот, который я придумал. Он позволяет использовать ключевое слово 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')
Более подробную информацию можно найти на странице рецептов.
Вот вариант решения Алека Томаса:
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',
))
type(class_name, (object,), dict(...))
?
- person terminus; 01.01.2012
Забавно, мне это просто понадобилось на днях, и я не смог найти достойную реализацию ... поэтому я написал свою собственную:
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))
Мне нравится использовать списки или наборы в качестве перечислений. Например:
>>> 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 категория.
Обычно я использую эту простую функцию для получения экземпляра динамически создаваемого класса.
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
Вот простая для чтения реализация выбранной идеи с некоторыми вспомогательными методами, которые, возможно, более Pythonic и более чистые в использовании, чем "reverse_mapping". Требуется 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)
Следуя Java-подобной реализации enum, предложенной Аароном Маенпаа, я сделал следующее. Идея заключалась в том, чтобы сделать его универсальным и доступным для анализа.
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()
Вариант (с поддержкой получения имени значения перечисления) для аккуратный ответ Алека Томаса:
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']