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

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

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

30 ответов


перечисления были добавлены в Python 3.4, как описано в PEP 435. Это также было система 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, и 2.4 на pypi.

для более продвинутых методов перечисления попробовать aenum библиотека (2.7, 3.3+, тот же автор, что и enum34. Код не полностью совместим между py2 и py3, например, вам понадобится __order__ в python 2).

  • использовать enum34, do $ pip install enum34
  • использовать aenum, у $ pip install aenum

установка enum (без номеров) установит совершенно другую и несовместимую версию.


from enum import Enum     # for enum34, or the stdlib version
# from aenum import Enum  # for the aenum version
Animal = Enum('Animal', 'ant bee cat dog')

Animal.ant  # returns <Animal.ant: 1>
Animal['ant']  # returns <Animal.ant: 1> (string lookup)
Animal.ant.name  # returns 'ant' (inverse lookup)

или:

class Animal(Enum):
    ant = 1
    bee = 2
    cat = 3
    dog = 4

в более ранних версиях одним из способов выполнения перечислений является:

def enum(**enums):
    return type('Enum', (), enums)

который используется так:

>>> Numbers = enum(ONE=1, TWO=2, THREE='three')
>>> Numbers.ONE
1
>>> Numbers.TWO
2
>>> Numbers.THREE
'three'

вы также можете легко поддерживать автоматическое перечисление с чем-то вроде это:

def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    return type('Enum', (), enums)

и используется вот так:

>>> Numbers = enum('ZERO', 'ONE', 'TWO')
>>> Numbers.ZERO
0
>>> Numbers.ONE
1

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

def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    reverse = dict((value, key) for key, value in enums.iteritems())
    enums['reverse_mapping'] = reverse
    return type('Enum', (), enums)

это перезаписывает что-либо с этим именем, но это полезно для рендеринга перечислений в выходных данных. Он будет вызывать KeyError, если обратное сопоставление не существует. Первый пример:

>>> Numbers.reverse_mapping['three']
'THREE'

до PEP 435 у Python не было эквивалента, но вы могли реализовать свой собственный.

Я предпочитаю держать его просто (я видел некоторые ужасно сложные примеры в сети), что-то вроде этого ...

class Animal:
    DOG = 1
    CAT = 2

x = Animal.DOG

В Python 3.4 (PEP 435), вы можете сделать перечисление базовый класс. Это дает вам немного дополнительной функциональности, описанной в ПЭП. Например, члены перечисления отличаются от целых чисел, и они состоит из name и value.

class Animal(Enum):
    DOG = 1
    CAT = 2

print(Animal.DOG)
# <Animal.DOG: 1>

print(Animal.DOG.value)
# 1

print(Animal.DOG.name)
# "DOG"

если вы не хотите вводить значения, используйте следующий ярлык:

class Animal(Enum):
    DOG, CAT = range(2)

Enum реализации может быть преобразован в списки и iterable. Порядок ее членов, порядок объявления и не имеет ничего общего с их значениями. Например:

class Animal(Enum):
    DOG = 1
    CAT = 2
    COW = 0

list(Animal)
# [<Animal.DOG: 1>, <Animal.CAT: 2>, <Animal.COW: 0>]

[animal.value for animal in Animal]
# [1, 2, 0]

Animal.CAT in Animal
# True

вот одна из реализаций:

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

вот его использование:

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

print(Animals.DOG)

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

dog, cat, rabbit = range(3)

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

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

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

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

Если вам нужны enum как только список имена выявление различных предметы, решение Марк Харрисон (выше) отличное:

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

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

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

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

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

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

stype = Stationery.Pen

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

для длинных списков перечислений или более сложных применений перечисления этих решений будет недостаточно. Вы могли бы посмотреть на рецепт Will Ware for моделирование перечислений в Python опубликовано на Поваренная Книга Python. Онлайн-версия этого доступна здесь.

Подробнее:

PEP 354: перечисления в Python имеет интересные детали предложения для перечисления в Python и почему оно было отклонено.


шаблон перечисления typesafe, который использовался в Java pre-JDK 5, имеет множество преимуществ. Как и в ответе Александру, вы создаете поля class и class level являются значениями перечисления; однако перечисление значения являются экземплярами класса, а не небольшими целыми числами. Это преимущество, что ваши значения перечисления не случайно сравниваются равными к малым целым числам вы можете управлять тем, как они печатаются, добавлять произвольные методы, если это полезно, и делают утверждения, используя 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 указал, что в дикой природе есть несколько библиотек перечислений, в том числе:


класс перечисления может быть однострочным.

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 не имеет встроенного эквивалентно enum и другие ответы есть идеи для реализации собственных (вы также можете быть заинтересованы в над верхней версия в поваренной книге Python).

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

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

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

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


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

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

для Python доступен backport 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, и 2.4. Это на Pypi as 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 == животное.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)

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

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


Хммм... Я полагаю, что ближе всего к перечислению будет словарь, определенный либо так:

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

или

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

затем, вы можете использовать символическое имя константы, как это:

mymonth = months['January']

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

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


Как использовать:

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.Опубликовано и два кортежа для использования в качестве вариантов в моделях Django.


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

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

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

if m in months:

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


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

перечисления создаются с использованием синтаксиса класса, что упрощает их создание читать и писать. Альтернативный способ создания в Функционального интерфейса API. Чтобы определить перечисление, Enum подкласса следующим образом:

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

Это лучший, который я видел: "перечисления первого класса в Python"

http://code.activestate.com/recipes/413486/

Он дает вам класс, и класс содержит все перечисления. Перечисления можно сравнивать друг с другом, но не имеют определенного значения; вы не можете использовать их как целое значение. (Сначала я сопротивлялся этому, потому что я привык к перечислениям C, которые являются целочисленными значениями. Но если вы не можете использовать его как целое число, вы не можете использовать это как целое число по ошибке, поэтому в целом я думаю, что это победа.) В каждом enum-это уникальное значение. Вы можете печатать перечисления, вы можете перебирать их, вы можете проверить, что значение перечисления находится "в" перечислении. Он довольно полный и скользкий.

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

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

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

сохранить его простым:

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

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

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

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

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

примечание: Я создал следующие примеры, пытаясь воспроизвести поведение нового стиля pygtk "перечисления" (например Gtk.Класса messagetype.Предупреждение)

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

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

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

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

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

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

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

    return Enum



>>> Numbers = enum_repr(int, ONE=1, TWO=2, THREE=3)
>>> repr(Numbers.ONE)
'<enum ONE of type Enum(int)>'
>>> str(Numbers.ONE)
'1'

новый стандарт в Python PEP 435, поэтому класс перечисления будет доступен в будущих версиях Python:

>>> from enum import Enum

однако, чтобы начать использовать его теперь вы можете установить исходные библиотеки это мотивировало PEP:

#sudo pip install flufl.enum   //or #sudo easy_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

при использовании других реализаций, расположенных здесь (также при использовании именованных экземпляров в моем примере), вы должны быть уверены, что никогда не пытаетесь сравнивать объекты из разных перечислений. Ибо вот возможная ловушка:--4-->

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


пакет перечисления из PyPI обеспечивает надежную реализацию перечислений. В более раннем ответе упоминалась ОПТОСОЗ 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())

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

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

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

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

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


вот подход с некоторыми различными характеристиками, которые я считаю ценными:

  • позволяет > и
  • может адресовать элемент по имени, свойству или индексу: x.a, x ['a'] или x[0]
  • поддерживает операции нарезки, такие как [:] или [-1]

и самое главное предотвращает сравнение между перечислениями различных типов!

основанный близко на http://code.activestate.com/recipes/413486-first-class-enums-in-python.

многие doctests включены здесь, чтобы проиллюстрировать, что отличается от этого подхода.

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

мне понадобились некоторые символьные константы в pyparsing для представления левой и правой ассоциативности двоичных операторов. Я использовал константы класса следующим образом:

# an internal class, not intended to be seen by client code
class _Constants(object):
    pass


# an enumeration of constants for operator associativity
opAssoc = _Constants()
opAssoc.LEFT = object()
opAssoc.RIGHT = object()

теперь, когда клиентский код хочет использовать эти константы, они могут импортировать всю перечисление через:

import opAssoc from pyparsing

перечисления уникальны, их можно протестировать с помощью " is "вместо"==", они не занимают большого места в моем коде для второстепенной концепции, и они легко импортируются в клиентский код. Они не поддерживают никакого причудливого поведения str (), но до сих пор это находится в YAGNI.