Гайд по использованию enum в Python
Модуль enum содержит в себе тип для перечисления значений с возможностью итерирования и сравнения. Его можно использовать для создания понятных обозначений вместо использования чисел (для которых приходится помнить, какое число что обозначает) или строк (в которых легко опечататься и не заметить).
Создание
Для создания перечисления необходимо создать класc, являющийся наследником класса 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))
Атрибуты класса Enum конвертируются в экземпляры при парсинге. Каждый экземпляр имеет параметр name , в котором хранится название, а также value , в котором хранится установленное значение.
$ python3 enum_create.py Member name: wont_fix Member value: 4
Итерирование
При итерировании по классу вы пройдёте по атрибутам.
# enum_iterate.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 for status in BugStatus: print(' = <>'.format(status.name, status.value))
Цикл будет идти по элементам в том порядке, в котором они указаны при создании класса. Названия и значения никак не влияют на порядок.
$ python3 enum_iterate.py new = 7 incomplete = 6 invalid = 5 wont_fix = 4 in_progress = 3 fix_committed = 2 fix_released = 1
Сравнение перечислений
Так как элементы перечислений не упорядочены, то они поддерживают сравнение только по названию или значению.
# 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)) # вывод ошибки в случае неудачи
Операторы больше и меньше порождают TypeError .
$ python3 enum_comparison.py Equality: False True Identity: False True Ordered by value: Cannot sort: '
IntEnum
Для создания перечислений с возможностью сравнения можно использовать 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
Уникальные значения в перечислениях
Элементы перечисления с одинаковыми значениями являются несколькими названиями, указывающими на один и тот же объект.
# 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(' = <>'.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 являются синонимами для других элементов, то они не появляются как элементы в циклах. Истинным считается название, указанное первым при объявлении.
$ 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
Если вы хотите, чтобы все элементы обязательно имели разные значения, то добавьте декоратор @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
Элементы с повторяющимися значениями будут вызывать ValueError во время интерпретации.
$ python3 enum_unique_enforce.py Traceback (most recent call last): File "enum_unique_enforce.py", line 11, in class BugStatus(enum.Enum): File ". /lib/python3.7/enum.py", line 848, in unique (enumeration, alias_details)) ValueError: duplicate values found in : by_design -> wont_fix, closed -> fix_released
Другой способ создания перечислений
Иногда удобнее не хардкодить элементы перечисления, а указывать их в более удобном виде. Для этого можно передать значения в конструктор класса:
# 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(' = <>'.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
Также аргумент name принимает список пар название — значение, либо аналогичный словарь.
# enum_programmatic_mapping.py import enum BugStatus = enum.Enum( value='BugStatus', names=[ ('new', 7), ('incomplete', 6), ('invalid', 5), ('wont_fix', 4), ('in_progress', 3), ('fix_committed', 2), ('fix_released', 1), ], ) print('All members:') for status in BugStatus: print(' = <>'.format(status.name, status.value))
Передача списка пар позволяет сохранить порядок элементов, аналогично случаю, где мы объявляли атрибуты.
$ 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
Другие типы значений
Значения элементов перечислений необязательно должны быть числами, они могут иметь любой тип. Если значение имеет тип tuple , то элементы передаются как отдельные аргументы в функцию __init()__ .
# 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))
В этом примере каждое значение является парой из числового id и списка строк, описывающих возможный переход из данного состояния.
$ python3 enum_tuple_values.py Name: BugStatus.in_progress Value: (3, ['new', 'fix_committed']) Custom attribute: ['new', 'fix_committed'] Using attribute: True
Для более сложных случаев tuple может быть плохим решением. Так как типом value может быть что угодно, мы можем использовать словари, чтобы обозначить различные параметры. Сложные значения передаются прямиком в __init()__ как единственный аргумент помимо self .
# enum_complex_values.py import enum class BugStatus(enum.Enum): new = < 'num': 7, 'transitions': [ 'incomplete', 'invalid', 'wont_fix', 'in_progress', ], >incomplete = < 'num': 6, 'transitions': ['new', 'wont_fix'], >invalid = < 'num': 5, 'transitions': ['new'], >wont_fix = < 'num': 4, 'transitions': ['new'], >in_progress = < 'num': 3, 'transitions': ['new', 'fix_committed'], >fix_committed = < 'num': 2, 'transitions': ['in_progress', 'fix_released'], >fix_released = < 'num': 1, 'transitions': ['new'], >def __init__(self, vals): self.num = vals['num'] self.transitions = vals['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))
Данный пример аналогичен предыдущему, но в нём используются словари вместо tuple для удобства.
$ python3 enum_complex_values.py Name: BugStatus.in_progress Value: Custom attribute: ['new', 'fix_committed'] Using attribute: True