Ещё одна реализация Enums для Python

    В прошлом году сообщество Python наконец-то договорилось о реализации перечислений. Было разработано соответствующее предложение PEP 435, его реализация уже есть в python 3.4.

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

    На текущий момент эксперименты закончены, библиотека хорошо показала себя в моих проектах, поэтому я решил поделиться ей с сообществом.

    В большинстве случаев, когда мы описываем отношение вида <имя, значение>, у нас имеется много информации, которую желательно привязать к имени: вспомогательный текст для пользовательского интерфейса, связи с родственными перечислениями, ссылки на другие объекты или функции. Приходится городить дополнительные структуры данных, что не есть хорошо — лишние сущности как-никак.

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

    Заодно добавил:

    • наследование;
    • несколько вспомогательных методов и проверок;
    • построение индексов по всем столбцам таблицы;
    • формирование обратных ссылок в связанных друг с другом отношениях;

    В итоге получилось вот такая вещь (примеры решил не дробить, чтобы не увеличивать и так длинное «полотно»):

    ########################
    # Базовое использование
    ########################
    
    from rels import Column, Relation
    
    # Enum и EnumWithText уже объявлены в библиотеке
    # и доступны как rels.Enum и rels.EnumWithText
    # тут их объявления приведены для упрощения понимания
    
    class Enum(Relation):             # объявляем абстраткное перечисление
        name = Column(primary=True)   # столбец с именами
        value = Column(external=True) # столбец со значениями
    
    
    # наследование — добавляем дополнительный столбец для какого-нибудь текста
    #                например, для использования в пользовательском интерфейсе
    class EnumWithText(Enum):
        text = Column()
    
    
    class SOME_CONSTANTS(Enum):       # объявляем конкретное перечисление
        records = ( ('NAME_1', 1),    # и указываем данные для него
                    ('NAME_2', 2))
    
    
    class SOME_CONSTANTS_WITH_TEXT(EnumWithText): # ещё одно конкретное перечисление
        records = ( ('NAME_1', 1, 'constant 1'),
                    ('NAME_2', 2, 'constant 2'))
    
    
    # Работаем с перечислениями
    
    # доступ к данным
    SOME_CONSTANTS.NAME_1.name == 'NAME_1'          # True
    SOME_CONSTANTS.NAME_1.value == 1                # True
    
    # получение элемента перечисления из «сырых» данных
    SOME_CONSTANTS(1) == SOME_CONSTANTS.NAME_1      # True
    
    # сравнения
    SOME_CONSTANTS.NAME_2 == SOME_CONSTANTS.NAME_2  # True
    SOME_CONSTANTS.NAME_2 != SOME_CONSTANTS.NAME_1  # True
    
    # теперь для проверок не надо всюду тягать импорты перечисления
    SOME_CONSTANTS.NAME_2.is_NAME_1                 # False
    SOME_CONSTANTS.NAME_2.is_NAME_2                 # True
    
    # каждый элемент перечисления — отдельный объект,
    # поэтому даже объекты с одинаковыми данными равны не будут
    SOME_CONSTANTS.NAME_2 != SOME_CONSTANTS_WITH_TEXT.NAME_2  # True
    SOME_CONSTANTS.NAME_1 != SOME_CONSTANTS_WITH_TEXT.NAME_1  # True
    
    # наследование — добавляем новые элементы
    class EXTENDED_CONSTANTS(SOME_CONSTANTS_WITH_TEXT):  # расширяем набор данных в перечислении
        records = ( ('NAME_3', 3, 'constant 3'), )       # добавляем ещё одно значение
    
    
    ########################
    # Индексы
    ########################
    
    class ENUM(Relation):
        name = Column(primary=True)   # для этого столбца имя индекса будет .index_name
        value = Column(external=True) # для этого столбца имя индекса будет .index_value
        text = Column(unique=False, index_name='by_key') # указываем своё имя для индекса
    
        records = ( ('NAME_1', 0, 'key_1'),
                    ('NAME_2', 1, 'key_2'),
                    ('NAME_3', 2, 'key_2'), )
    
    # если данные в столбце уникальны, значением в словаре будет элемент перечисления
    ENUM.index_name # {'NAME_1': ENUM.NAME_1, 'NAME_2': ENUM.NAME_2,  'NAME_3': ENUM.NAME_3}
    
    # если данные в столбце не уникальны, значением в словаре будет список элементов перечисления
    ENUM.by_key     # {'key_1': [ENUM.NAME_1], 'key_2': [ENUM.NAME_2, ENUM.NAME_3]}
    
    
    ########################
    # Обратные ссылки
    ########################
    
    # объявляем отношение, на которое будем ссылаться
    class DESTINATION_ENUM(Relation):
        name = Column(primary=True)
        val = Column()
    
        records = ( ('STATE_1', 'value_1'),
                    ('STATE_2', 'value_2') )
    
    # объявляем отношение, которое будет ссылаться
    class SOURCE_ENUM(Relation):
        name = Column(primary=True)
        val = Column()
        rel = Column(related_name='rel_source')
    
        records = ( ('STATE_1', 'value_1', DESTINATION_ENUM.STATE_1),
                    ('STATE_2', 'value_2', DESTINATION_ENUM.STATE_2) )
    
    # проверяем работу ссылок
    DESTINATION_ENUM.STATE_1.rel_source == SOURCE_ENUM.STATE_1 # True
    DESTINATION_ENUM.STATE_2 == SOURCE_ENUM.STATE_2.rel        # True
    

    Отдельно замечу, что не обязательно перечисления объявлять в коде. Во многих случаях, удобнее ограничиться объявлением модели данных, а сами данные грузить из сторонних источников, например, электронных таблиц.

    Репозиторий и подробная документация на github

    P.S. библиотека разрабатывалась в расчёте на Python 2.7, с третьим не проверялась.

    Only registered users can participate in poll. Log in, please.

    Нужны ли альтернативные реализации перечислений для Питона?

    Support the author
    Share post

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 17

      +4
      Рискну получить минусов и спрошу, что же такое перечисления, для чего они нужны в Пайтоне в том или ином виде, каково их практическое применение и насколько они затратны по производительности?
        +2
        Интересует чисто пользовательский ответ на эти вопросы, ибо PEP435 я читал, ну а с необходимостью использования такой структуры данных пока не сталкивался. Ну и словари, конечно же, использую для подобных нужд (если я верно сравниваю).
        • UFO just landed and posted this here
            0
            Разумно, согласен с Вами. Даже теперь есть идеи, где бы я мог использовать их в своем коде.
            +3
            Перечисление в классическом виде — это просто набор констант, принадлежащих к одному и тому же типу. В Python не принято проверять тип данных, поэтому перечисления были введены, как мне кажется, просто как удобная и привычная абстракция, знакомая многим. По большому счёту в Python легко можно обойтись без использования перечислений, но их присутствие в стандартной библиотеке с версии 3.4 точно не будет лишним. Кому это нужно, тот и будет использовать.

            Мне удобно использовать перечисления в Python и инкапсулировать в них какие-то связанные данные. Ну, вот, например, как-то так:

            from collections import namedtuple
            from flufl import enum
            
            # Данные, связанные с перечислением WorldSide
            _ = namedtuple('WorldSideInfo', ['angle', 'mode'])
            
            class WorldSide(enum.Enum):
                """Класс перечисления определяет стороны света"""
            
                North = _(90, (0, 0, 0))
                NorthEast = _(45, (2, 1, 1))
                East = _(0, (0, 0, 0))
                SouthEast = _(-45, (2, 1, 1))
                South = _(-90, (0, 0, 0))
                SouthWest = _(-135, (2, 1, 1))
                West = _(180, (0, 0, 0))
                NorthWest = _(135, (2, 1, 1))
            
            
            # Используем
            ws_angle = WorldSide.NorthWest.value.angle
            
            
              +2
              Благодарю Вас за хороший пример. Действительно, качественное применение этого нововведения не будет лишним. А ещё, Вы натолкнули меня на интересные мысли по применению перечислений. В моем коде по работе со свойствами объектов-изображений с одинаковыми числовыми типами значений они могут очень пригодиться для описания свойств этих самых объектов.

              Выходит, мое сравнение перечислений со словарями ошибочно?
            +1
            википедия: Перечисляемый тип

            Производительность зависит от конкретной реализации. В случае Rels никаких особых затыков нет, чёрной магии под капотом не делается.
              +1
              То есть, потребление памяти минимальное?
                0
                Да.

                Естественно, память линейно зависит от количества элементов перечисления и количества столбцов в таблице.

                В случае работы с перечислениями, скорее критична не память (т.к. их достаточно мало создаётся, по сравнению с другими объектами), а вычислительная сложность выполнения базовых операций с ними (получение значений, сравнения и т.п.). В этом плане всё тоже хорошо.
                  0
                  В таком случае, благодарю Вас за новый инструмент в моей работе с любимым языком :)
                    0
                    Спасибо, надеюсь он окажется полезным.
                    Если будут замечания или предложения по развитию библиотеки — пишите.
                      0
                      Кстати, вот я не спец в такого рода делах, однако, эта структура, что называется, thread-safe?
                        0
                        Да.
            +2
            В Python 2.7 мне хватает возможностей модуля flufl.enum. Собственно, его реализация и легла в основу PEP435.
              0
              Тогда согласен: удобно теперь будет иметь такой функционал в стандартном комплекте.
              Осталось мне теперь по-тихоньку переносить свой код в третью ветку.
              Вам это удается или ещё не пробовали?
              +1
              Мне для Enum'а понравилось одно из решений на StackOverflow:
              class Colors:
                  class Red: pass
                  class Green: pass
                  class Blue: pass
              
              • UFO just landed and posted this here

              Only users with full accounts can post comments. Log in, please.