Як я можу представити "Enum" в Python?


1143

Я в основному розробник C #, але зараз працюю над проектом в Python.

Як я можу представити еквівалент Enum в Python?

Відповіді:


2688

Енуми були додані до 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'

1
Мені не вдалося зрозуміти, чому вони передали kwargs (** найменування) у методі enum (* послідовний, ** названий)? Будь ласка, поясніть. Без кваргів теж буде працювати. Я це перевірив.
Seenu S

Було б непогано оновити функцію Python 2 на сумісність з функціональним API Python 3 Enum (ім'я, значення)
bscan

Var kwargs ( **named) у функції enum для старих версій полягає у підтримці спеціальних значень:enum("blue", "red", "green", black=0)
Éric Araujo

823

До 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

51
Ні, це змінна категорія.
Георг Шоллі

246
Python є динамічним за замовчуванням. Немає поважних причин застосовувати безпеку часу компіляції на такій мові, як Python, особливо коли такої немає. І інша річ ... хороша модель хороша лише в тому контексті, в якому вона була створена. Хороший зразок також може бути замінений або зовсім марний, залежно від використовуваних інструментів.
Олександру Недельку

20
@Longpoke, якщо у вас 100 значень, ви напевно робите щось не так;) Мені подобаються числа, пов’язані з моїми перерахунками ... вони легко записуються (проти рядків), можуть легко зберігатися в базі даних і сумісні з перерахунок C / C ++, що полегшує розмежування.
Олександру Недельку

50
Я використовую це, при цьому цифри замінюються на object().
Тобу

9
Оригінальний PEP354 вже не просто відхиляється, але зараз позначений заміненим. PEP435 додає стандартний Enum для Python 3.4. Дивіться python.org/dev/peps/pep-0435
Пітер Хансен

322

Ось одна реалізація:

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

Ось його використання:

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

print(Animals.DOG)

51
Відмінно. Це можна вдосконалити за допомогою переосмислення __setattr__(self, name, value)та, можливо, __delattr__(self, name)так, що якщо ви випадково напишіть Animals.DOG = CAT, це мовчки не вдасться.
Joonas Pulakka

15
@shahjapan: Цікаво, але відносно повільно: тест робиться для кожного доступу, наприклад Animals.DOG; також значення констант є рядками, так що порівняння з цими константами відбувається повільніше, ніж якщо, скажімо, цілі числа були дозволені як значення.
Ерік О Лебігот

3
@shahjapan: Я б заперечував, що це рішення не настільки розбірливе, як, наприклад, більш короткі рішення Олександру чи Марка. Хоча це цікаве рішення. :)
Ерік О Лебігот

Я спробував використовувати 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
Harshith JV

8
@ AndréTerra: як перевірити наявність встановленого членства в try-exceptблоці?
bgusach

210

Якщо вам потрібні числові значення, ось найшвидший спосіб:

dog, cat, rabbit = range(3)

У Python 3.x ви також можете додати в кінці зірковий заповнювач, який буде вбирати всі решта значень діапазону, якщо ви не проти витрачати пам'ять і не можете рахувати:

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

1
Але це може зайняти більше пам’яті!
MJ

Я не бачу сенсу зіркового заповнювача, враховуючи, що Python перевірить кількість значень для розпакування (тому він зробить підрахунок для вас).
Габріель

@GabrielDevillers, я думаю, що Python створить виняток, якщо буде вказано невідповідність кількості елементів у кортежі.
Марк Гаррісон

1
Дійсно, це робиться в моєму тесті (Python2,3), але це означає, що будь-яка помилка підрахунку програміста буде виявлена ​​під час першого тесту (з повідомленням, що дає правильний підрахунок).
Габріель

1
Я не можу порахувати. Чи може зірковий заповнювач також поправити мої фінанси?
javadba

131

Найкраще рішення для вас буде залежати від того, що ви вимагаєте від своєї підробки 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 та чому він був відхилений.


7
з rangeви можете опустити перший аргумент , якщо це 0
ToonAlfrink

Ще один фальшивий перелік, який відповідає деяким цілям 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)]
majkelx

Це найкраща відповідь
Юрій Позняк

78

Модель 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, зокрема:


16
Я думаю, що це дуже поганий підхід. Animal.DOG = Animal ("собака") Animal.DOG2 = Animal ("собака") стверджують Animal.DOG == Animal.DOG2 не вдається ...
Плутанина

11
@Confusion Користувач не повинен викликати конструктор, той факт, що навіть конструктор є деталі реалізації, і вам доведеться повідомляти тому, хто коли-небудь використовує ваш код, що вводити нові значення перерахування не має сенсу і що вихідний код не буде "роби правильно". Звичайно, це не заважає вам реалізувати Animal.from_name ("собака") -> Animal.DOG.
Аарон Маенпаа

13
"перевага в тому, що ваші значення перерахунків ненавмисно порівнюються з малими цілими числами". Яка перевага в цьому? Що не так у порівнянні вашого перерахунку з цілими числами? Особливо, якщо ви зберігаєте enum в базі даних, зазвичай ви хочете, щоб він зберігався як цілі числа, тому вам доведеться в якийсь момент порівнювати його з цілими числами.
ibz

3
@Aaaron Maenpaa. правильно. Це все-таки зламаний і надто складний спосіб зробити це.
ааронастерлінг

4
@AaronMcSmooth Це насправді залежить від того, ви приїжджаєте з точки зору С "Енуми - це лише імена на пару інт" або більш об'єктно-орієнтованого підходу, де значення перерахунків є фактичними об'єктами та мають методи (як це перераховує в Java 1.5 є, і який тип безпечної схеми перерахунку збирався). Особисто мені не подобаються заяви переключення, тому я схиляюся до значень перерахунків, які є фактичними об'єктами.
Аарон Маенпаа

61

Клас 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 2.4 (так, старий застарілий сервер) кортежі не індексуються. Я вирішив замінити список.
Массімо

Я спробував це в зошиті Юпітера і виявив, що він не працюватиме як однолінійне визначення, але, якщо встановити визначення гетатра на другий (з відступом), буде прийнято.
користувач5920660

Це рішення дозволить мені використовувати inключове слово для пошуку членів, які є акуратними. Приклад використання:'Claimed' in Enum(['Unclaimed', 'Claimed'])
Farzad Abdolhosseini

51

Отже, я згоден. Давайте не будемо застосовувати безпеку типу в 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]

Мені це подобається, але ви могли б також зафіксувати значення для ефективності з кортежем? Я пограв з цим і придумав версію, яка встановлює self.values ​​з аргументів init . Приємно вміти заявляти Animal = Enum('horse', 'dog', 'cat'). Я також ловлю ValueError в getattr у разі відсутності елемента в self.values ​​- здається, краще підняти AttributeError замість наданого рядка імені. Мені не вдалося змусити метаклас працювати в Python 2.7 на основі моїх обмежених знань у цій галузі, але мій індивідуальний клас Enum прекрасно працює з прямими методами екземпляра.
trojjer

49

У Python немає вбудованого еквівалента enum, а в інших відповідях є ідеї для реалізації вашої власної (вас також може зацікавити верхня версія в кулінарній книзі Python).

Однак у ситуаціях, коли enumв C вимагатиметься заклик, я зазвичай закінчую лише прості рядки : через те, як реалізуються об'єкти / атрибути, (C) Python оптимізований для роботи дуже швидко з короткими рядками, так що не було б Насправді будь-яка користь від використання цілих чисел не буде. Для захисту від помилок друку / недійсних значень можна вставити чеки у вибраних місцях.

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

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

(Одним з недоліків порівняно з класом є те, що ви втрачаєте перевагу автозаповнення)


2
Я вважаю за краще це рішення. Мені подобається використовувати вбудовані типи, де це можливо.
Seun Osewa

Ця версія насправді не вгорі. У ньому просто багато постачається тестового коду
Casebash

1
Власне, "правильна" версія є в коментарях і набагато складніша - основна версія має незначну помилку.
Casebash

39

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

Для отримання додаткової інформації зверніться до пропозиції . Офіційна документація, ймовірно, скоро відбудеться.


33

Я вважаю за краще визначати переліки в Python так:

class Animal:
  class Dog: pass
  class Cat: pass

x = Animal.Dog

Це більше захисту від помилок, ніж використання цілих чисел, оскільки вам не потрібно турбуватися про те, щоб цілі числа були унікальними (наприклад, якщо ви сказали, що Dog = 1 і Cat = 1 вас накрутить).

Це більше помилок, ніж використання рядків, оскільки вам не доведеться турбуватися про помилки помилок (наприклад, x == "catt" виходить з ладу, але x == Animal.Catt - це виняток під час виконання).


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

11
ІМХО було б чистіше, якби ви змінилися enum(names)на enum(*names)- тоді ви можете скинути зайві дужки при його виклику.
Кріс Лутц

Мені подобається такий підхід. Я насправді змінив його, щоб встановити значення атрибуту на той самий рядок, що і ім'я, яке має приємне властивість Animal.DOG == 'DOG', тому вони автоматично строкують себе для вас. (
Неодмінно

23

З Python 3.4 буде офіційна підтримка переліків. Ви можете знайти документацію та приклади тут на сторінці документації Python 3.4 .

Перерахування створюються за допомогою синтаксису класу, що полегшує їх читання та запис. Альтернативний метод створення описаний у Functional API. Щоб визначити перерахування, підклас Enum наступним чином:

from enum import Enum
class Color(Enum):
     red = 1
     green = 2
     blue = 3

Зараз підтримується також зворотне перенесення. Це шлях.
srock

22

Гммм ... Я гадаю, що найближчим до перерахунку буде словник, визначений так:

months = {
    'January': 1,
    'February': 2,
    ...
}

або

months = dict(
    January=1,
    February=2,
    ...
)

Тоді ви можете використовувати символічну назву для констант на зразок цієї:

mymonth = months['January']

Є й інші варіанти, наприклад, список кортежів або кортеж кортежів, але словник - єдиний, який надає вам "символічний" (постійний рядок) спосіб доступу до значення.

Редагувати: Мені подобається і відповідь Олександра!


І найбільше ви можете легко ітератувати словник, якщо вам потрібно отримати доступ до його значень, як потрібні його рядкові значення, щоб вони відображалися як об'єднані елементи. Тому використовуйте словник замість перерахунків.
LEMUEL ADANE

22

Інша, дуже проста, реалізація 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

діяти так, як очікувалося, якщо ви використовуєте версію, яка заповнює послідовні значення числа.


20

Що я використовую:

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.


17

davidg рекомендує використовувати дикти. Я б пішов ще на крок і використав набори:

months = set('January', 'February', ..., 'December')

Тепер ви можете перевірити, чи відповідає значення одному з таких значень у наборі:

if m in months:

як dF, однак я зазвичай просто використовую рядкові константи замість enums.


так !, набагато краще, якщо ви успадковуєте набір і надаєте метод getattr !
шахпапан

17

Не ускладнювати:

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

16

Це найкраще, що я бачив: "Енуми першого класу в 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)

Цей рецепт був використаний як основа для ПЕП, який був відхилений. python.org/dev/peps/pep-0354 Одне розширення, яке мені подобається: значення enum повинні мати змінну члена, яка дозволяє отримати внутрішнє ціле значення. Не повинно бути можливим помилково передати enum цілому числу, тому .__int__()метод повинен створювати виняток для enum; але має бути спосіб вивести значення. І повинно бути можливість встановити конкретні цілі значення протягом часу визначення класу, щоб ви могли використовувати enum для таких речей, як константи в statмодулі.
steveha

14

Я мав нагоду потребувати класу 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

Мені дуже подобається використання суперкласу з власним метакласом, щоб легко визначити перерахунки. Чого тут не вистачає, це метод __contains__. Я хотів би мати можливість перевірити, що дана змінна є частиною enum - здебільшого тому, що я хочу, щоб переліки були допустимими параметрами функції.
xorsyst

Це насправді трохи підрізана версія тієї, яку я створив спочатку (яку ви можете знайти тут: enum_strict.py ) v, яка визначає __instancecheck__метод. Заняття не є набором екземплярів, так 1 in Fruitце абсурдно. Однак пов'язана версія підтримує, isinstance(1, Fruit)що було б більш правильним щодо поняття класів та примірників.
SingleNegationElimination

Але забувши заняття і мислити з точки зору переліків, тоді є сенс думати про них як про колекцію. Наприклад, у мене може бути перелік режимів відкриття файлів (MODE.OPEN, MODE.WRITE тощо). Я хочу перевірити параметри своєї функції: якщо режим у MODE: читає набагато краще, ніж isintance (режим, режим)
xorsyst

Я розмістив щось дуже схоже, яке підтримує більше, ніж просто ints, і це документально підтверджено і перевірено на GitHub: github.com/hmeine/named_constants
hans_meine

12

Новим стандартом в 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

10
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!


9

Мені дуже подобається рішення Алека Томаса (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'

ця ідея "базового" типу акуратна :)
MestreLion

Так, зауважте, що ви також можете це зробити за допомогою нового Python 3.4 Enum: python.org/dev/peps/pep-0435/#other-derived-enumerations
bj0

7

Пакет 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'

5

Пропозиція Олександра використовувати константи класів для перерахунків працює досить добре.

Мені також подобається додати словник для кожного набору констант, щоб шукати людське для читання представлення рядків.

Це виконує дві цілі: а) це простий спосіб гарненько роздрукувати перерахунок; б) словник логічно групує константи, щоб ви могли перевірити на членство.

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

5

Ось підхід із деякими різними характеристиками, який я вважаю цінним:

  • дозволяє> і <порівняння на основі порядку в перерахунку, а не лексичного порядку
  • може адресувати елемент за назвою, властивістю чи індексом: xa, x ['a'] або x [0]
  • підтримує операції нарізки типу [:] або [-1]

і найголовніше запобігає порівнянню між перерахунками різних типів !

Грунтуючись на 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


3

Це рішення є простим способом отримання класу для перерахунку, визначеного як список (не більше дратівливих цілих призначень):

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',
))

2
Це дійсно старовинний спосіб створення класів. Чому б просто не використовувати type(class_name, (object,), dict(...))натомість?
термін

3

Незважаючи на те, що початкова пропозиція про перерахунок, PEP 354 , була відхилена років тому, вона все одно повертається. Якась кількість перерахунків мала бути додана до 3,2, але її відсунули до 3,3, а потім забули. А тепер є PEP 435, призначений для включення в Python 3.4. Довідкова реалізація PEP 435 є flufl.enum.

Станом на квітень 2013 року, мабуть, існує загальний консенсус щодо того, що щось слід додати до стандартної бібліотеки в 3,4 - до тих пір, поки люди можуть погодитись, що це має бути "щось". Це важка частина. Дивіться теми, що починаються тут і тут , і півтора десятка інших ниток на початку місяців 2013 року.

Тим часом, щоразу, коли це з’являється, на PyPI, ActiveState тощо з'являються нові проекти та впровадження, тому якщо вам не подобається дизайн FLUFL, спробуйте пошук PyPI .


3

Скористайтеся наступним.

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

Я використовував це для вибору моделі Джанго , і це виглядає дуже пітонічно. Це насправді не Енум, але він робить свою роботу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.