Как я могу представить Enum в Python?

1143

Я в основном разработчик C #, но сейчас я работаю над проектом на Python.

Как я могу представить эквивалент Enum в Python?

сектант
источник

Ответы:

2689

Перечисления были добавлены в 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. Код не полностью совместим между 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 (** named) в метод enum (* sequential, ** named)? Пожалуйста, объясните. Без kwargs также это будет работать. Я проверил это.
Seenu S
Было бы неплохо обновить функцию Python 2, чтобы она была совместима с функциональным API-интерфейсом Enum (имя, значения) Python 3
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. Например, члены перечисления отличаются от целых и состоят из 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
Jundiaius
источник
51
Нет, это переменная класса.
Георг Шолли
246
Python является динамическим по умолчанию. Нет никаких веских причин для обеспечения безопасности во время компиляции в языке, подобном Python, особенно когда его нет. И еще одна вещь ... хороший шаблон хорош только в том контексте, в котором он был создан. Хороший шаблон также может быть заменен или полностью бесполезен, в зависимости от инструментов, которые вы используете.
Александру Недельку
20
@ Longpoke, если у вас есть 100 значений, то вы определенно делаете что-то не так;) Мне нравятся числа, связанные с моими перечислениями ... они легко пишутся (против строк), могут быть легко сохранены в базе данных и совместимы с перечисление C / C ++, облегчающее маршалинг.
Александру Недельку
50
Я использую это, с номерами, замененными на object().
Тобу
9
Оригинальный PEP354 больше не просто отклонен, но теперь помечен как замененный. PEP435 добавляет стандартный Enum для Python 3.4. См. Python.org/dev/peps/pep-0435
Питер Хансен
323

Вот одна реализация:

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__()метода переопределения . Я предполагаю, что это должно работать так же: класс Enum (объект): def __init __ (self, enum_string_list): если тип (enum_string_list) == список: для enum_string в enum_string_list: setattr (self, enum_string, enum_string) else: повысить AttributeError
Harshith JV
8
@ AndréTerra: как проверить наличие членства в try-exceptблоке?
bgusach
210

Если вам нужны числовые значения, вот самый быстрый способ:

dog, cat, rabbit = range(3)

В Python 3.x вы также можете добавить помеченный заполнитель в конце, который впитает все оставшиеся значения диапазона, если вы не возражаете потратить память и не можете сосчитать:

dog, cat, rabbit, horse, *_ = range(100)
Mark Harrison
источник
1
Но это может занять больше памяти!
MJ
Я не вижу смысла в помеченном местозаполнителе, учитывая, что Python проверит количество распаковываемых значений (поэтому он сделает подсчет за вас).
Габриэль Девиллерс
@GabrielDevillers, я думаю, что Python выдаст исключение, если есть несоответствие по количеству элементов в кортеже, который нужно назначить.
Марк Харрисон
1
Действительно, в моем тесте это происходит (Python2,3), но это означает, что любая ошибка подсчета от программиста будет обнаружена в первом тесте (с сообщением, предоставляющим правильный счет).
Габриэль Девиллерс
1
Я не могу сосчитать. Может ли помеченный заполнитель также исправить мои финансы?
Джавадба
131

Лучшее решение для вас будет зависеть от того, что вы требуете от вашей подделки enum .

Простое перечисление:

Если вам нужен enumтолько список имен, идентифицирующих различные предметы , решение от Mark Harrison (выше) будет отличным:

Pen, Pencil, Eraser = range(0, 3)

Использование rangeтакже позволяет установить любое начальное значение :

Pen, Pencil, Eraser = range(9, 12)

В дополнение к вышесказанному, если вам также требуется, чтобы элементы принадлежали к какому-либо контейнеру , вставьте их в класс:

class Stationery:
    Pen, Pencil, Eraser = range(0, 3)

Чтобы использовать элемент enum, вам теперь нужно будет использовать имя контейнера и имя элемента:

stype = Stationery.Pen

Комплексное перечисление:

Для длинных списков перечислений или более сложных применений перечислений этих решений будет недостаточно. Вы можете посмотреть на рецепт Уилла Уэра для имитации перечислений в Python, опубликованный в Python Cookbook . Онлайн версия этого доступна здесь .

Больше информации:

PEP 354. Перечисления в Python содержат интересные детали предложения enum в Python и причины его отклонения.

Ashwin
источник
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

Тип enum типа safeafe, который использовался в Java pre-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, в том числе:

Аарон Маенпаа
источник
16
Я думаю, что это очень плохой подход. Animal.DOG = Animal ("собака") Animal.DOG2 = Animal ("собака") утверждает Animal.DOG == Animal.DOG2 не удается ...
Путаница
11
@Confusion Пользователь не должен вызывать конструктор, тот факт, что есть даже конструктор, является деталью реализации, и вам нужно сообщить тем, кто когда-либо использует ваш код, что создание новых значений перечисления не имеет смысла и что выход из кода не будет "делать правильные вещи". Конечно, это не мешает вам реализовать Animal.from_name ("dog") -> Animal.DOG.
Аарон Маэнпаа
13
«преимущество в том, что ваши значения перечисления не случайно сравниваются равными маленьким целым числам» В чем преимущество? Что плохого в сравнении вашего перечисления с целыми числами? Особенно, если вы храните enum в базе данных, вы обычно хотите, чтобы он хранился как целые числа, поэтому вам придется сравнить его с целыми числами в какой-то момент.
IBZ
3
@Aaaron Maenpaa. правильный. Это все еще сломанный и слишком сложный способ сделать это.
Ааронастерлинг
4
@AaronMcSmooth Это действительно зависит от того, подходите ли вы с точки зрения C «Enums - это просто имена для пары целых чисел» или более объектно-ориентированного подхода, где значения enum являются фактическими объектами и имеют методы (как перечисления в Java 1.5 и какой тип безопасного перечисления собирался). Лично мне не нравятся операторы switch, поэтому я склоняюсь к значениям enum, которые являются реальными объектами.
Аарон Маэнпаа
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)]
оборота Zoetic
источник
Я думаю, что это самое простое и самое элегантное решение. В Python 2.4 (да, старый унаследованный сервер) кортежи не имеют индекса. Я решил заменить список.
Массимо
Я попробовал это в записной книжке Jupyter и обнаружил, что это не будет работать как однострочное определение, но что размещение определения getattr во второй (с отступом) строке будет принято.
user5920660
Это решение позволяет мне использовать 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 ​​из args в init . Приятно иметь возможность заявить Animal = Enum('horse', 'dog', 'cat'). Я также ловлю ValueError в getattr в случае отсутствия элемента в self.values ​​- кажется, что вместо этого лучше вызвать AttributeError с предоставленной строкой имени. Я не мог заставить метакласс работать в Python 2.7, основываясь на моих ограниченных знаниях в этой области, но мой пользовательский класс Enum отлично работает с методами прямого экземпляра.
Trojjer
49

Python не имеет встроенного эквивалента enum, а в других ответах есть идеи для реализации вашего собственного (вас также может заинтересовать версия over over в кулинарной книге Python).

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

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

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

(Одним из недостатков по сравнению с использованием класса является то, что вы теряете преимущество автозаполнения)

Дф.
источник
2
Я предпочитаю это решение. Мне нравится использовать встроенные типы, где это возможно.
Сеун Осева
Эта версия не слишком хороша. У него просто много
тестового
1
На самом деле, «правильная» версия в комментариях гораздо сложнее - в основной версии есть небольшая ошибка.
Casebash
39

2013-05-10, Гвидо согласился принять PEP 435 в стандартную библиотеку Python 3.4. Это означает, что Python наконец-то имеет встроенную поддержку перечислений!

Для Python 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 и 2.4 имеется бэкпорт. Это на Pypi как enum34 .

Декларация:

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

Представление:

>>> print(Color.red)
Color.red
>>> print(repr(Color.red))
<Color.red: 1>

Итерация:

>>> for color in Color:
...   print(color)
...
Color.red
Color.green
Color.blue

Программный доступ:

>>> Color(1)
Color.red
>>> Color['blue']
Color.blue

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

Данило Барген
источник
33

Я предпочитаю определять перечисления в Python так:

class Animal:
  class Dog: pass
  class Cat: pass

x = Animal.Dog

Это более устойчиво к ошибкам, чем использование целых чисел, поскольку вам не нужно беспокоиться о том, чтобы целые числа были уникальными (например, если вы сказали, что Dog = 1 и Cat = 1, то вас облажают)

Это более устойчиво к ошибкам, чем использование строк, так как вам не нужно беспокоиться об опечатках (например, x == "catt" завершается с ошибкой, но x == Animal.Catt является исключением времени выполнения).

mbac32768
источник
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)
user18695
источник
11
ИМХО, было бы чище, если бы вы изменили enum(names)на enum(*names)- тогда вы могли бы убрать лишние скобки при вызове.
Крис Лутц
Мне нравится этот подход. Я на самом деле изменил его, установив в качестве значения атрибута ту же строку, что и имя, у которого есть замечательное свойство Animal.DOG == 'DOG', поэтому они автоматически сами себя переводят в строку. (Очень помогает в распечатке отладочной информации.)
Тед Мельчарек
23

Начиная с Python 3.4 будет официальная поддержка перечислений. Вы можете найти документацию и примеры здесь на странице документации Python 3.4 .

Перечисления создаются с использованием синтаксиса классов, что облегчает их чтение и запись. Альтернативный метод создания описан в Функциональном 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']

Существуют и другие варианты, например, список кортежей или кортеж кортежей, но единственный словарь, который предоставляет вам «символический» (постоянная строка) способ доступа к значению.

Редактировать: мне тоже нравится ответ Александру!

dguaraglia
источник
И больше всего вы можете легко перебрать словарь, если вам нужен доступ к его значениям, как если бы вам нужно, чтобы его строковые значения отображались в виде элементов комбинированного списка. Так что используйте словарь в качестве замены для перечислений.
LEMUEL ADANE
22

Еще одна очень простая реализация перечисления в 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.

Luciano Ramalho
источник
17

Дэвидг рекомендует использовать диктовку. Я бы пошел еще дальше и использовал наборы:

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

Теперь вы можете проверить, соответствует ли значение одному из значений в наборе следующим образом:

if m in months:

как и dF, я обычно просто использую строковые константы вместо перечислений.

смокинг
источник
да, намного лучше, если вы наследуете set и предоставляете метод 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
опасности89
источник
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)
cfi
источник
Этот рецепт был использован в качестве основы для ПКП, который был отклонен. python.org/dev/peps/pep-0354 Одно расширение, которое мне нравится: значения enum должны иметь переменную-член, которая позволяет вывести внутреннее целочисленное значение. Не должно быть возможности привести перечисление к целому числу по ошибке, поэтому .__int__()метод должен вызвать исключение для перечисления; но должен быть способ получить значение. И должно быть возможно установить конкретные целочисленные значения во время определения класса, чтобы вы могли использовать перечисление для таких вещей, как константы в statмодуле.
Steveha
14

Мне приходилось нуждаться в классе 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()все производят наиболее полезный выход можно соответственно название enumartion, его целое значение, и выражение , которое вычисляется на Python назад в перечислении.
  • Перечисляемые значения, возвращаемые конструктором, строго ограничены предопределенными значениями, никаких случайных значений перечисления.
  • Перечисленные значения являются синглетонами; их можно строго сравнить сis
TokenMacGuy
источник
Мне действительно нравится использование суперкласса с его собственным метаклассом, чтобы упростить определение перечислений. Чего здесь не хватает, так это метода __contains__. Я хотел бы иметь возможность проверить, что данная переменная является частью перечисления - главным образом потому, что я хочу перечисления для допустимых значений параметра функции.
xorsyst
На самом деле это слегка урезанная версия той, которую я изначально создал (которую вы можете найти здесь: enum_strict.py ) v, которая определяет __instancecheck__метод. Классы не являются коллекциями экземпляров, поэтому 1 in Fruitэто абсурд. Тем не менее, связанная версия поддерживает, isinstance(1, Fruit)что было бы более правильным с точки зрения понятия классов и экземпляров.
SingleNegationElimination
Но если забыть о классах и думать с помощью перечислений, то имеет смысл думать о них как о коллекции. Например, у меня может быть список режимов открытия файлов (MODE.OPEN, MODE.WRITE и т. Д.). Я хочу проверить параметры моей функции: если режим в режиме: читает намного лучше, чем isintance (режим, режим)
xorsyst
Я выложил нечто очень похожее, которое поддерживает больше, чем просто целые, и задокументировано и протестировано на 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
Riaz Rizvi
источник
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

Хлоп!

estani
источник
9

Мне очень нравится решение Алекса Томаса (http://stackoverflow.com/a/1695250):

def enum(**enums):
    '''simple constant "enums"'''
    return type('Enum', (object,), enums)

Это элегантный и чистый вид, но это просто функция, которая создает класс с указанными атрибутами.

С небольшой модификацией функции мы можем заставить ее действовать немного более enumy:

ПРИМЕЧАНИЕ. Я создал следующие примеры, пытаясь воспроизвести поведение нового стиля pygtk 'enums' (например, Gtk.MessageType.WARNING).

def enum_base(t, **enums):
    '''enums with a base class'''
    T = type('Enum', (t,), {})
    for key,val in enums.items():
        setattr(T, key, T(val))

    return T

Это создает перечисление на основе указанного типа. В дополнение к предоставлению доступа к атрибутам, как и в предыдущей функции, он ведет себя так, как вы ожидаете от Enum относительно типов. Он также наследует базовый класс.

Например, целочисленные перечисления:

>>> Numbers = enum_base(int, ONE=1, TWO=2, THREE=3)
>>> Numbers.ONE
1
>>> x = Numbers.TWO
>>> 10 + x
12
>>> type(Numbers)
<type 'type'>
>>> type(Numbers.ONE)
<class 'Enum'>
>>> isinstance(x, Numbers)
True

Еще одна интересная вещь, которую можно сделать с помощью этого метода, - это настроить конкретное поведение, переопределив встроенные методы:

def enum_repr(t, **enums):
    '''enums with a base class and repr() output'''
    class Enum(t):
        def __repr__(self):
            return '<enum {0} of type Enum({1})>'.format(self._name, t.__name__)

    for key,val in enums.items():
        i = Enum(val)
        i._name = key
        setattr(Enum, key, i)

    return Enum



>>> Numbers = enum_repr(int, ONE=1, TWO=2, THREE=3)
>>> repr(Numbers.ONE)
'<enum ONE of type Enum(int)>'
>>> str(Numbers.ONE)
'1'
bj0
источник
эта идея «базового» типа аккуратна :)
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'
Ashwin
источник
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

Вот вариант решения Алекса Томаса :

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
оборота Рой Хёнджин Хан
источник
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

Хотя первоначальное предложение enum, PEP 354 , было отклонено несколько лет назад, оно продолжает возвращаться. Какой-то enum был предназначен для добавления в 3.2, но он был перенесен в 3.3, а затем забыт. И теперь есть PEP 435, предназначенный для включения в Python 3.4. Эталонная реализация PEP 435 есть flufl.enum.

По состоянию на апрель 2013 года, по-видимому, существует общее согласие о том, что что- то должно быть добавлено в стандартную библиотеку в 3.4 - при условии, что люди могут договориться о том, каким должно быть это «что-то». Это сложная часть. Смотрите темы, начинающиеся здесь и здесь , и полдюжины других тем в первые месяцы 2013 года.

Между тем, каждый раз, когда это происходит, появляется множество новых проектов и реализаций в PyPI, ActiveState и т. Д., Поэтому, если вам не нравится дизайн FLUFL, попробуйте поиск PyPI .

abarnert
источник
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']

Я использовал это для выбора модели Django , и это выглядит очень питонно. Это на самом деле не Enum, но он делает свою работу.

Natim
источник