python3 - перечисления python




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

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

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

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

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

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

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

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


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

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

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

if m in months:

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


В 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

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


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

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

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

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

class Animal:
    DOG = 1
    CAT = 2

x = Animal.DOG

В Python 3.4 ( PEP 435 ) вы можете сделать Enum базовым классом. Это дает вам немного дополнительной функциональности, описанной в PEP. Например, члены перечисления отличаются от целых чисел и состоят из 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 могут быть преобразованы в списки и являются итерабельными . Порядок его членов - это порядок объявления и не имеет никакого отношения к их значениям. Например:

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

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

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


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

Класс 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)]

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

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

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

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

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

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

    return T

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

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

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

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

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

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

    return Enum



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

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

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

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

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

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

Пакет перечислений из 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'

Предложение Александру об использовании констант класса для перечислений работает достаточно хорошо.

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

Это служит двум целям: а) он обеспечивает простой способ для правильной печати вашего перечисления и b) словарь логически группирует константы, чтобы вы могли проверить членство.

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

Типичный шаблон перечисления, который использовался в 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 в дикой природе, в том числе:


Что я использую:

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.


Это лучший из тех, что я видел: «Firstum Enums в Python»

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

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)

Я предпочитаю определять перечисления в Python следующим образом:

class Animal:
  class Dog: pass
  class Cat: pass

x = Animal.Dog

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

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


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

  • позволяет> и <сравнение на основе порядка в перечислении, а не лексический порядок
  • может обращаться к элементу по имени, свойству или индексу: 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

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

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

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


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)




enums