Реклама ООО Таймвэб
Реклама ООО Таймвэб

Гайд по использованию Enum в Python

Обсудить
Гайд по использованию Enum в Python
Реклама. ООО «ТаймВэб». erid: LjN8KB3Re

В современной разработке программного обеспечения концепция перечислений (enums) является важным элементом. Они позволяют определить набор именованных значений, чтобы упростить разработчикам чтение и понимание кода, уменьшить вероятность ошибок и сделать программы более устойчивыми. В Python перечисления реализованы с помощью специального модуля enum. В этой статье мы рассмотрим, как использовать Enum в Python, покажем основные примеры и дадим рекомендации по применению этого инструмента в реальных проектах.

Важно! Эта статья предназначена для пользователей, которые уже имеют базовые знания языка Python и понимают концепцию перечислений (enums) в программировании. 

Что такое перечисления enum в Python

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

Перечисления помогают организовать связанные значения в структурированный формат. Они часто используются для кодов ответов, математических операций, дней недели и т.д. Например, в C# есть встроенный тип перечисления для цветов, который упрощает выбор цвета из списка без необходимости знать его код. 

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

Содержание модуля enum в Python

Прежде всего, важно не путать эти два понятия, которые имеют одинаковые названия enum:

  • Перечисления enum. Это тип данных, который позволяет создать именованный набор констант. Они могут быть реализованы в разных языках программирования, не только в Python.
  • Модуль enum в Python. Это стандартный модуль, который предоставляет инструменты для создания перечислений в Python. С его помощью можно легко определить классы перечислений и работать с ними.

Проще говоря, перечисления – это концепция или тип данных, а модуль enum – это конкретный инструмент в Python для работы с этим типом данных.

Основные компоненты модуля включают в себя:

  • Enum. Базовый класс для создания перечислений. Он позволяет определить набор именованных констант с уникальными значениями.
  • IntEnum. Расширяет функционал Enum, причем все элементы перечисления являются подклассами типа int.
  • Flag. Перечисление, предназначенное для создания набора битовых флагов. Элементы такого перечисления можно комбинировать с помощью побитовых операторов.
  • IntFlag. Комбинирует функционал IntEnum и Flag, позволяя создавать перечисления битовых флагов, где каждое значение является числом.

Дополнительные инструменты:

  • Декоратор unique. Гарантирует, что все значения в перечислении уникальны. Если два элемента имеют одно и то же значение, тогда будет вызвано исключение.
  • Помощник auto. Позволяет автоматически присваивать значения элементам перечисления. При его использовании значения начинаются с единицы и увеличиваются на единицу для каждого следующего элемента.

Создание перечислений в Python

Для создания перечисления в Python, необходимо определить класс, наследующий enum.Enum. Элементы этого класса определяются как атрибуты класса, и каждому из них присваивается уникальное значение. 

Вот пример создания перечисления:

#  enum_create.py
import enum

class BugStatus(enum.Enum):

    new = 7
    incomplete = 6
    invalid = 5
    wont_fix = 4
    in_progress = 3
    fix_committed = 2
    fix_released = 1

print('\nMember name: {}'.format(BugStatus.wont_fix.name)) 
print('Member value: {}'.format(BugStatus.wont_fix.value))

При запуске этого кода вы увидите следующее:

$ python3 enum_create.py

Member name: wont_fix
Member value: 4

Каждый атрибут в Enum автоматически становится экземпляром перечисления при определении класса. Эти экземпляры имеют два основных атрибута:

  • name, который содержит имя элемента;
  • value, который содержит его значение.

Нужно учитывать, что значения элементов перечисления могут быть любого типа, будь то целые числа, строки или даже кортежи. Если не требуется конкретное значение для элемента, можно использовать помощник auto(), который автоматически присвоит следующее доступное значение.

Итерирование по перечислениям в Python

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

from enum import Enum
    Weapon = Enum('Weapon', 'SWORD BOW DAGGER CLUB', start=10)

    for weapon in Weapon:
        print(weapon)

    for weapon in Weapon:
        print(weapon.name, weapon.value)

В этом коде мы проходим по всем элементам перечисления с помощью цикла for. Каждый элемент представляет собой пару «имя-значение».

Сначала делаются итерации по элементам перечисления через цикл for.

for weapon in Weapon:
        print(weapon)

После этого отображаются их значения и имена.

for weapon in Weapon:
        print(weapon.name, weapon.value)

В результате выполнения этого кода вы увидите:

Weapon.SWORD
Weapon.BOW
Weapon.DAGGER
Weapon.CLUB
SWORD 10
BOW 11
DAGGER 12
CLUB 13

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

Сравнение элементов перечисления в Python

В Python элементы перечисления (Enum) уникальны, и их можно сравнивать либо по названию (name), либо по значению (value). Однако нужно помнить, что, хотя элементы перечисления имеют значения, они не обязательно упорядочены по этим значениям.

Рассмотрим следующий пример:

#  enum_comparison.py
import enum

class BugStatus(enum.Enum):

    new = 7
    incomplete = 6
    invalid = 5
    wont_fix = 4
    in_progress = 3
    fix_committed = 2
    fix_released = 1

actual_state = BugStatus.wont_fix
desired_state = BugStatus.fix_released

print('Equality:',
      actual_state == desired_state,
      actual_state == BugStatus.wont_fix)  # проверка на равенство
print('Identity:',
      actual_state is desired_state,
      actual_state is BugStatus.wont_fix)  # проверка на то, это один и тот же элемент или нет
print('Ordered by value:')
try:
    print('\n'.join('  ' + s.name for s in sorted(BugStatus)))  # пытаемся упорядочить
except TypeError as err:
    print('  Cannot sort: {}'.format(err))  # вывод ошибки в случае неудачи

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

Использование IntEnum для создания упорядоченных перечислений

В Python класс IntEnum из модуля enum позволяет создавать перечисления, элементы которых являются подклассами int. Одной из ключевых особенностей IntEnum является возможность сравнивать и упорядочивать элементы перечисления по их значению.

Рассмотрим следующий пример:

#  enum_intenum.py
import enum

class BugStatus(enum.IntEnum):

    new = 7
    incomplete = 6
    invalid = 5
    wont_fix = 4
    in_progress = 3
    fix_committed = 2
    fix_released = 1

print('Ordered by value:')
print('\n'.join('  ' + s.name for s in sorted(BugStatus)))  # упорядочивание по значению

 

$ python3 enum_intenum.py

Ordered by value:
  fix_released
  fix_committed
  in_progress
  wont_fix
  invalid
  incomplete
  new

Как видите, благодаря IntEnum мы можем легко упорядочить элементы перечисления по их значениям, что было бы невозможно с обычным Enum.

Обеспечение уникальности значений в перечислениях Python

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

Пример:

#  enum_aliases.py
import enum

class BugStatus(enum.Enum):

    new = 7
    incomplete = 6
    invalid = 5
    wont_fix = 4
    in_progress = 3
    fix_committed = 2
    fix_released = 1

    by_design = 4
    closed = 1

for status in BugStatus:
    print('{:15} = {}'.format(status.name, status.value))

print('\nSame: by_design is wont_fix: ',
      BugStatus.by_design is BugStatus.wont_fix)
print('Same: closed is fix_released: ',
      BugStatus.closed is BugStatus.fix_released)

В результате выполнения данного кода, синонимы (такие как by_design и closed) не будут отображены при итерации по перечислению. Только первоначальные элементы (wont_fix и fix_released) будут учитываться.

Поскольку closed и by_design – это синонимы для ряда других объектов, они не отображаются как элементы в циклах. Подлинным является то значение, которое указывалось первым при объявлении.

$ python3 enum_aliases.py

new             = 7
incomplete      = 6
invalid         = 5
wont_fix        = 4
in_progress     = 3
fix_committed   = 2
fix_released    = 1

Same: by_design is wont_fix:  True
Same: closed is fix_released:  True

Если вы хотите гарантировать уникальность значений для всех элементов перечисления, используйте декоратор @enum.unique:

#  enum_unique_enforce.py
import enum

@enum.unique
class BugStatus(enum.Enum):
    new = 7
    incomplete = 6
    invalid = 5
    wont_fix = 4
    in_progress = 3
    fix_committed = 2
    fix_released = 1

    # This will trigger an error with unique applied.
    by_design = 4
    closed = 1

С применением декоратора @unique, любые попытки определить элементы перечисления с дублирующимися значениями приведут к возникновению ошибки ValueError на этапе интерпретации.

$ python3 enum_unique_enforce.py

Traceback (most recent call last):
  File "enum_unique_enforce.py", line 11, in <module>
    class BugStatus(enum.Enum):
  File ".../lib/python3.7/enum.py", line 848, in unique
    (enumeration, alias_details))
ValueError: duplicate values found in <enum 'BugStatus'>:
by_design -> wont_fix, closed -> fix_released

Программное создание перечислений в Python

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

Пример:

#  enum_programmatic_create.py
import enum

BugStatus = enum.Enum(
    value='BugStatus',
    names=('fix_released fix_committed in_progress '
           'wont_fix invalid incomplete new'),
)

print('Member: {}'.format(BugStatus.new))

print('\nAll members:')
for status in BugStatus:
    print('{:15} = {}'.format(status.name, status.value))

В данном коде аргумент value определяет название перечисления. Аргумент names принимает строку, содержащую названия элементов перечисления. Если вы предоставляете строку, она будет разделена на отдельные имена, а пробелы и запятые используются в качестве разделителей. Значения для членов автоматически присваиваются, начиная с 1.

$ python3 enum_programmatic_create.py

Member: BugStatus.new

All members:
fix_released    = 1
fix_committed   = 2
in_progress     = 3
wont_fix        = 4
invalid         = 5
incomplete      = 6
new             = 7

Также аргумент names может принимать список пар «название-значение» или словарь. Использование списка пар позволяет сохранить порядок элементов, что аналогично объявлению атрибутов в классе.

$ python3 enum_programmatic_mapping.py

All members:
new             = 7
incomplete      = 6
invalid         = 5
wont_fix        = 4
in_progress     = 3
fix_committed   = 2
fix_released    = 1

Использование различных типов значений в перечислениях Python

В Python значения элементов перечисления могут быть любого типа, не обязательно числового. В качестве примера рассмотрим перечисление, где каждому элементу присвоено значение в виде кортежа (tuple).

Пример:

#  enum_tuple_values.py
import enum

class BugStatus(enum.Enum):

    new = (7, ['incomplete',
               'invalid',
               'wont_fix',
               'in_progress'])
    incomplete = (6, ['new', 'wont_fix'])
    invalid = (5, ['new'])
    wont_fix = (4, ['new'])
    in_progress = (3, ['new', 'fix_committed'])
    fix_committed = (2, ['in_progress', 'fix_released'])
    fix_released = (1, ['new'])

    def __init__(self, num, transitions):
        self.num = num
        self.transitions = transitions

    def can_transition(self, new_state):
        return new_state.name in self.transitions

print('Name:', BugStatus.in_progress)
print('Value:', BugStatus.in_progress.value)
print('Custom attribute:', BugStatus.in_progress.transitions)
print('Using attribute:',
      BugStatus.in_progress.can_transition(BugStatus.new))

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

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

Класс BugStatus также содержит метод can_transition, который проверяет, можно ли выполнить переход из текущего состояния в новое.

$ python3 enum_tuple_values.py

Name: BugStatus.in_progress
Value: (3, ['new', 'fix_committed'])
Custom attribute: ['new', 'fix_committed']
Using attribute: True

Использование целых чисел в перечислениях Python

В Python при создании перечислений (Enum) часто используются целые числа в качестве значений элементов. 

Этот подход предоставляет ряд преимуществ:

  1. Краткость. Целые числа позволяют компактно представить состояния или категории в вашем коде.
  2. Удобство. Многие API и системы при работе с перечислениями ожидают числовые значения, что делает их интеграцию более простой.
  3. Универсальность. Целые числа являются универсальным типом данных, который легко преобразуется и сравнивается.

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

Заключение

Перечисления в Python предоставляют мощный и гибкий инструмент для представления уникальных наборов именованных значений. Они обогащают ваш код, делая его более читаемым и надежным, избавляя от ошибок, связанных с «магическими» числами или строками. Благодаря различным вариантам использования перечислений, таким как IntEnum, Flag и декоратор @unique, Python предоставляет разработчикам широкий спектр возможностей для различных сценариев. 

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

Наши постоянные авторы и читатели делятся лайфхаками, основанными на личном опыте. Полная свобода самовыражения.

Комментарии

С помощью соцсетей
У меня нет аккаунта Зарегистрироваться
С помощью соцсетей
У меня уже есть аккаунт Войти
Инструкции по восстановлению пароля высланы на Ваш адрес электронной почты.
Пожалуйста, укажите email вашего аккаунта
Ваш баланс 10 ТК
1 ТК = 1 ₽
О том, как заработать и потратить Таймкарму, читайте в этой статье
Чтобы потратить Таймкарму, зарегистрируйтесь на нашем сайте