В современной разработке программного обеспечения концепция перечислений (enums) является важным элементом. Они позволяют определить набор именованных значений, чтобы упростить разработчикам чтение и понимание кода, уменьшить вероятность ошибок и сделать программы более устойчивыми. В Python перечисления реализованы с помощью специального модуля enum. В этой статье мы рассмотрим, как использовать Enum в Python, покажем основные примеры и дадим рекомендации по применению этого инструмента в реальных проектах.
Что такое перечисления 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) часто используются целые числа в качестве значений элементов.
Этот подход предоставляет ряд преимуществ:
- Краткость. Целые числа позволяют компактно представить состояния или категории в вашем коде.
- Удобство. Многие API и системы при работе с перечислениями ожидают числовые значения, что делает их интеграцию более простой.
- Универсальность. Целые числа являются универсальным типом данных, который легко преобразуется и сравнивается.
Тем не менее, важно помнить, что в контексте перечислений само числовое значение часто не имеет особого семантического значения. Главное – это уникальность каждого элемента внутри перечисления и его имя, которое передает смысл.
Заключение
Перечисления в Python предоставляют мощный и гибкий инструмент для представления уникальных наборов именованных значений. Они обогащают ваш код, делая его более читаемым и надежным, избавляя от ошибок, связанных с «магическими» числами или строками. Благодаря различным вариантам использования перечислений, таким как IntEnum, Flag и декоратор @unique, Python предоставляет разработчикам широкий спектр возможностей для различных сценариев.
Надеемся, что эта статья помогла вам лучше понять и оценить возможности, которые предоставляют перечисления в Python, а также вдохновила вас использовать их в ваших будущих проектах.
Комментарии