Відповіді:
Енуми були додані до Python 3.4, як описано в PEP 435 . Він також підтримується до 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 та 2.4 на піпі.
Для більш досконалих методів Enum спробуйте бібліотеку 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'
**named
) у функції enum для старих версій полягає у підтримці спеціальних значень:enum("blue", "red", "green", black=0)
До PEP 435 у Python не було еквівалента, але ви можете реалізувати свій власний.
Сам мені подобається, щоб це було просто (я бачив кілька жахливих складних прикладів в мережі), щось подібне ...
class Animal:
DOG = 1
CAT = 2
x = Animal.DOG
У Python 3.4 ( PEP 435 ) ви можете зробити Enum базовим класом. Це дає вам трохи додаткової функціональності, описаної в PEP. Наприклад, члени enum відрізняються від цілих чисел, і вони складаються з a name
і a 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
object()
.
Ось одна реалізація:
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
, це мовчки не вдасться.
Animals.DOG
; також значення констант є рядками, так що порівняння з цими константами відбувається повільніше, ніж якщо, скажімо, цілі числа були дозволені як значення.
setattr()
функцію всередині __init__()
методу замість __getattr__()
методу overidding . Я припускаю, що це має працювати так само: клас Enum (об'єкт): def __init __ (самоврядування, enum_string_list): якщо тип (enum_string_list) == список: для enum_string в enum_string_list: setattr (self, enum_string, enum_string) інше: підвищити AttributeError
try-except
блоці?
Якщо вам потрібні числові значення, ось найшвидший спосіб:
dog, cat, rabbit = range(3)
У Python 3.x ви також можете додати в кінці зірковий заповнювач, який буде вбирати всі решта значень діапазону, якщо ви не проти витрачати пам'ять і не можете рахувати:
dog, cat, rabbit, horse, *_ = range(100)
Найкраще рішення для вас буде залежати від того, що ви вимагаєте від своєї підробки enum
.
Простий перерахунок:
Якщо вам потрібен enum
лише список імен, що ідентифікують різні предмети , рішення Марка Гаррісона (вище) чудово:
Pen, Pencil, Eraser = range(0, 3)
Використання a range
також дозволяє встановити будь-яке початкове значення :
Pen, Pencil, Eraser = range(9, 12)
На додаток до вищезазначеного, якщо ви також вимагаєте, щоб елементи належали до якогось контейнера , тоді вставте їх у клас:
class Stationery:
Pen, Pencil, Eraser = range(0, 3)
Щоб використовувати елемент enum, тепер вам потрібно буде використовувати ім’я контейнера та ім'я елемента:
stype = Stationery.Pen
Складний перелік:
Для довгих списків переліку чи більш складних застосувань перерахувань цих рішень буде недостатньо. Ви можете поглянути на рецепт Вілла Уаре для імітації перерахувань на Python, опублікований у кулінарній книзі Python . Онлайн версія, яка доступна тут .
Більше інформації:
PEP 354: Перерахування в Python містять цікаві деталі пропозиції про перерахування в Python та чому він був відхилений.
range
ви можете опустити перший аргумент , якщо це 0
my_enum = dict(map(reversed, enumerate(str.split('Item0 Item1 Item2'))))
. Потім my_enum
може бути використаний при пошуку, наприклад, my_enum['Item0']
може бути індексом у послідовності. Ви можете загорнути результат функції str.split
у функцію, яка видає виняток, якщо є дублікати.
Flag1, Flag2, Flag3 = [2**i for i in range(3)]
Модель typesafe enum, яка використовувалася в Java pre-JDK 5, має ряд переваг. Так само, як і у відповіді Олександру, ви створюєте поля класу та рівня класу - значення перерахунків; однак значення enum - це екземпляри класу, а не малі цілі числа. Це має перевагу в тому, що значення перерахунків ненавмисно не порівнюються з малими цілими числами, ви можете керувати тим, як вони надруковані, додавати довільні методи, якщо це корисно, і робити твердження за допомогою речовини:
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'])
Отже, я згоден. Давайте не будемо застосовувати безпеку типу в 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 прекрасно працює з прямими методами екземпляра.
У Python немає вбудованого еквівалента enum
, а в інших відповідях є ідеї для реалізації вашої власної (вас також може зацікавити верхня версія в кулінарній книзі Python).
Однак у ситуаціях, коли enum
в C вимагатиметься заклик, я зазвичай закінчую лише прості рядки : через те, як реалізуються об'єкти / атрибути, (C) Python оптимізований для роботи дуже швидко з короткими рядками, так що не було б Насправді будь-яка користь від використання цілих чисел не буде. Для захисту від помилок друку / недійсних значень можна вставити чеки у вибраних місцях.
ANIMALS = ['cat', 'dog', 'python']
def take_for_a_walk(animal):
assert animal in ANIMALS
...
(Одним з недоліків порівняно з класом є те, що ви втрачаєте перевагу автозаповнення)
10.05.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 - це виняток під час виконання).
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)
- тоді ви можете скинути зайві дужки при його виклику.
З Python 3.4 буде офіційна підтримка переліків. Ви можете знайти документацію та приклади тут на сторінці документації Python 3.4 .
Перерахування створюються за допомогою синтаксису класу, що полегшує їх читання та запис. Альтернативний метод створення описаний у Functional API. Щоб визначити перерахування, підклас Enum наступним чином:
from enum import Enum
class Color(Enum):
red = 1
green = 2
blue = 3
Гммм ... Я гадаю, що найближчим до перерахунку буде словник, визначений так:
months = {
'January': 1,
'February': 2,
...
}
або
months = dict(
January=1,
February=2,
...
)
Тоді ви можете використовувати символічну назву для констант на зразок цієї:
mymonth = months['January']
Є й інші варіанти, наприклад, список кортежів або кортеж кортежів, але словник - єдиний, який надає вам "символічний" (постійний рядок) спосіб доступу до значення.
Редагувати: Мені подобається і відповідь Олександра!
Інша, дуже проста, реалізація enum в 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.
davidg рекомендує використовувати дикти. Я б пішов ще на крок і використав набори:
months = set('January', 'February', ..., 'December')
Тепер ви можете перевірити, чи відповідає значення одному з таких значень у наборі:
if m in months:
як dF, однак я зазвичай просто використовую рядкові константи замість enums.
Не ускладнювати:
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"
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__()
метод повинен створювати виняток для enum; але має бути спосіб вивести значення. І повинно бути можливість встановити конкретні цілі значення протягом часу визначення класу, щоб ви могли використовувати enum для таких речей, як константи в stat
модулі.
Я мав нагоду потребувати класу Enum для розшифровки бінарного формату файлів. Особливості, які мені хотілося, - це стисле визначення enum, можливість вільного створення примірників enum або цілим значенням, або рядком, і корисна repr
есенція. Ось що я закінчив:
>>> 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)
що було б більш правильним щодо поняття класів та примірників.
Новим стандартом в 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
Yikes!
Мені дуже подобається рішення Алека Томаса (http://stackoverflow.com/a/1695250):
def enum(**enums):
'''simple constant "enums"'''
return type('Enum', (object,), enums)
Це елегантний і чистий вигляд, але це лише функція, яка створює клас із заданими атрибутами.
Трохи модифікуючи функцію, ми можемо змусити її діяти трохи більше «перегною»:
ПРИМІТКА. Я створив такі приклади, намагаючись відтворити поведінку нового стилю 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'
Пакет 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'
Пропозиція Олександра використовувати константи класів для перерахунків працює досить добре.
Мені також подобається додати словник для кожного набору констант, щоб шукати людське для читання представлення рядків.
Це виконує дві цілі: а) це простий спосіб гарненько роздрукувати перерахунок; б) словник логічно групує константи, щоб ви могли перевірити на членство.
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
Ось варіант рішення Алека Томаса :
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(...))
натомість?
Незважаючи на те, що початкова пропозиція про перерахунок, PEP 354 , була відхилена років тому, вона все одно повертається. Якась кількість перерахунків мала бути додана до 3,2, але її відсунули до 3,3, а потім забули. А тепер є PEP 435, призначений для включення в Python 3.4. Довідкова реалізація PEP 435 є flufl.enum
.
Станом на квітень 2013 року, мабуть, існує загальний консенсус щодо того, що щось слід додати до стандартної бібліотеки в 3,4 - до тих пір, поки люди можуть погодитись, що це має бути "щось". Це важка частина. Дивіться теми, що починаються тут і тут , і півтора десятка інших ниток на початку місяців 2013 року.
Тим часом, щоразу, коли це з’являється, на PyPI, ActiveState тощо з'являються нові проекти та впровадження, тому якщо вам не подобається дизайн FLUFL, спробуйте пошук PyPI .
Скористайтеся наступним.
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']
Я використовував це для вибору моделі Джанго , і це виглядає дуже пітонічно. Це насправді не Енум, але він робить свою роботу.