Enum (скорочено від Enumerations) – надають можливість визначати набір пов’язаних іменованих констант, котрі можна використовувати для представлення значень зі змістовними іменами, замість використання так званих магічних чисел, або жорстко закодованих рядків. Це робить код зрозумілішим та легшим в підтримці.
Зміст запису:
- Створення enum у Python
- Автоматичне встановлення значень членам enum
- Ітерація по enum
- Порівняння членів enum
- Унікальні значення в enum Python
- Використання enum в операторах if та match
- Розширення enum новою поведінкою
- Enum з multiple значеннями
- Інші класи enum
Створення enum у Python
Python надає можливість створювати власні перерахування за допомогою вбудованого модуля enum. Для цього використовується клас Enum, де кожний член перерахування визначається як атрибут класу.
Щоб створити перерахування, слід оголосити клас, який успадковує клас enum.Enum. Члени перерахування здаються як атрибути цього класу. Приклад:
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3Нижче розглянуто різноманітні приклади доступу до членів enum у Python:
# Accessing enum members
print(list(Color))
# [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 3>]
# Accessing enum member by name
print(Color.GREEN)
# Color.GREEN
# Accessing enum member by name
print(Color['GREEN'])
# Color.GREEN
# Accessing enum member by value
print(Color(2))
# Color.GREEN
# Accessing enum member value
print(Color.GREEN.value)
# 2
# Accessing enum member name
print(Color.GREEN.name)
# GREENАтрибути класу Enum конвертуються в його екземпляри, кожен з котрих має власні атрибути name (зберігає ім’я), та value (зберігає встановлене значення).
Зазвичай членам enum в якості значень встановлюють послідовні цілі числа. Однак у Python значення членів enum можуть бути будь-якого типу, включаючи користувацькі типи. Приклад використання рядкових значень:
from enum import Enum
class Size(Enum):
S = 'small'
M = 'medium'
L = 'large'
print(list(Size))
# [<Size.S: 'small'>, <Size.M: 'medium'>, <Size.L: 'large'>]Ще один спосіб оголошення enum у Python, використання функціонального API, де потрібно просто викликати Enum з відповідними аргументами, так само як при виклику функції:
from enum import Enum
Color = Enum('Color', ['RED', 'GREEN', 'BLUE'])
print(list(Color))
# [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 3>]Слід звернути увагу, що в такому випадку значення членів автоматично встановлюються в послідовні цілі числа, починаючи з 1. Також при потребі можна встановити власні значення:
from enum import Enum
Color = Enum('Color', {'RED': 'R', 'GREEN': 'G', 'BLUE': 'B'})
print(list(Color))
# [<Color.RED: 'R'>, <Color.GREEN: 'G'>, <Color.BLUE: 'B'>]Наостанок слід додати, що оскільки члени enum є константами, Python забороняє присвоювати їм нові значення під час виконання:
from enum import Enum
class Size(Enum):
S = 'small'
M = 'medium'
L = 'large'
Size.S = 's'
# AttributeError: Cannot reassign members.Автоматичне встановлення значень членам enum
Модуль enum у Python надає функцію auto(), яка дає змогу автоматично встановлювати значення для членів enum:
from enum import Enum, auto
class Priority(Enum):
LOW = auto()
MEDIUM = auto()
HIGH = auto()
print(list(Priority))
# [<Priority.LOW: 1>, <Priority.MEDIUM: 2>, <Priority.HIGH: 3>]За замовчуванням, функція auto() присвоює членам enum послідовні цілі числа, в якості значень (починаючи з 1). При потребі змінити поведінку за замовчуванням, слід реалізувати в класі перерахування метод _generate_next_value_(), який використовується auto() для генерації значень:
from enum import Enum, auto
class Priority(Enum):
def _generate_next_value_(name, start, count, last_values):
return name[0]
LOW = auto()
MEDIUM = auto()
HIGH = auto()
print(list(Priority))
# [<Priority.LOW: 'L'>, <Priority.MEDIUM: 'M'>, <Priority.HIGH: 'H'>]Ітерація по enum
Є можливість перебору членів enum за допомогою циклу for:
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
for color in Color:
print(color)
"""
Color.RED
Color.GREEN
Color.BLUE
"""Цикл буде йти по атрибутам у тому порядку, в якому вони вказані при оголошенні класу. Назви та значення ніяк не впливають на порядок.
Порівняння членів enum
Члени enum можна порівнювати за допомогою операторів тотожності (is) та рівності (==):
from enum import Enum
class Status(Enum):
ACTIVE = 1
INACTIVE = 2
PENDING = 3
print(Status.ACTIVE == Status.ACTIVE) # True
print(Status.ACTIVE == Status.INACTIVE) # False
print(Status.ACTIVE is Status.ACTIVE) # True
print(Status.ACTIVE is Status.INACTIVE) # FalseОператори більше та менше не підтримуються, при спробі порівняння буде піднято виняток TypeError:
print(Status.ACTIVE > Status.INACTIVE)
# TypeError: '>' not supported between instances of 'Status' and 'Status'В такому випадку слід використовувати клас IntEnum, який підтримує порівняння членів перерахування за їх значеннями:
from enum import IntEnum
class Priority(IntEnum):
LOW = 1
MEDIUM = 2
HIGH = 3
print(Priority.HIGH > Priority.MEDIUM) # True
print(Priority.MEDIUM >= Priority.MEDIUM) # True
print(Priority.LOW == 1) # True
print(Priority.HIGH > 2) # TrueУнікальні значення в enum Python
Члени enum з однаковими значеннями, є різними іменами що вказують на один і той самий об’єкт:
from enum import Enum
class Color(Enum):
RED = 1
CRIMSON = 1
BLUE = 2У цьому прикладі RED та CRIMSON вказують на один об’єкт:
print(Color.RED == Color.CRIMSON) # True
print(Color.RED is Color.CRIMSON) # TrueПри спробі отримати name для CRIMSON, повертається перше ім’я з оголошення класу перерахування (саме воно вважається істинним):
print(Color.CRIMSON.name) # REDТакож, оскільки CRIMSON є синонімом для RED, його не буде відображено при спробі перебору enum у циклі:
for color in Color:
print(color)
# Color.RED
# Color.BLUEЯкщо потрібно щоб усі члени enum мали лише унікальні значення, слід додати декоратор @unique перед оголошенням класу:
import enum
@enum.unique
class Color(enum.Enum):
RED = 1
CRIMSON = 1
BLUE = 2
# ValueError: duplicate values found in <enum 'Color'>: CRIMSON -> REDВ такому випадку, члени з повторюваними значеннями будуть піднімати виняток ValueError під час виконання.
Використання enum в операторах if та match
Оператори if...elif та match...case є поширеними місцями для використання enum. Обидві конструкції дають змогу виконувати різні дії в залежності від певних умов. Приклади:
from enum import Enum
class Status(Enum):
NEW = 1
IN_PROGRESS = 2
DONE = 3
status = Status.IN_PROGRESS
if status is Status.NEW:
print('Task created')
elif status is Status.IN_PROGRESS:
print('Task performed')
elif status is Status.DONE:
print('Task completed')
# Task performedfrom enum import Enum
class Status(Enum):
NEW = 1
IN_PROGRESS = 2
DONE = 3
status = Status.IN_PROGRESS
match status:
case Status.NEW:
print('Task created')
case Status.IN_PROGRESS:
print('Task performed')
case Status.DONE:
print('Task completed')
# Task performedРозширення Enum новою поведінкою
До цього ми працювали з enum за замовчуванням (тобто використовували стандартні можливості та поведінку). При потребі розширити стандартну поведінку, класи enum у Python надають можливість додавати методи з реалізацією необхідної функціональності (так само як це робиться з будь-яким звичайним класом Python). Приклад:
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
def describe(self):
return f'This is {self.name.lower()}'
color = Color.RED
print(color.describe())
# This is redРозглянемо цікавіший приклад з реалізацією шаблону Strategy. Уявімо, що нам потрібен клас, який дозволяє застосовувати одну з двох стратегій сортування списку: за зростанням або спаданням. Реалізуємо це використовуючи enum:
from enum import Enum
class Sort(Enum):
ASCENDING = 1
DESCENDING = 2
def __call__(self, values):
return sorted(values, reverse=self is Sort.DESCENDING)
numbers = [5, 3, 1, 4, 2]
print(Sort.ASCENDING(numbers)) # [1, 2, 3, 4, 5]
print(Sort.DESCENDING(numbers)) # [5, 4, 3, 2, 1]У цьому прикладі, кожний член enum є callable (тобто викликається як функція), завдяки реалізації методу __call__ у класі. Також, саме в __call__ зашита логіка сортування. Таким чином, маємо enum котрий визначає дві стратегії сортування: Sort.ASCENDING та Sort.DESCENDING.
Enum з multiple значеннями
Є можливість оголошувати enum, де кожен член має більше ніж одне значення:
from enum import Enum
class Fruit(Enum):
APPLE = ('Red', 52)
BANANA = ('Yellow', 89)
ORANGE = ('Orange', 47)
def __init__(self, color, calories):
self.color = color
self.calories = calories
print(Fruit.APPLE.name) # APPLE
print(Fruit.APPLE.value) # ('Red', 52)
print(Fruit.APPLE.color) # Red
print(Fruit.APPLE.calories) # 52У цьому прикладі, значенням кожного члена enum є кортеж з двох елементів, котрі завдяки реалізації методу __init__ зберігаються у відповідні атрибути екземпляра: color та calories.
Інші класи enum
Окрім класу Enum, модуль enum надає декілька додаткових класів, котрі дозволяють створювати перерахування зі специфічною поведінкою. Розглянемо деякі з них.
IntEnum
Успадкування від класу IntEnum дозволяє створювати перерахування, яке також є підкласом int (тобто його члени матимуть також властивості цілих чисел). Приклад:
from enum import IntEnum
class Size(IntEnum):
S = 1
M = 2
L = 3
print(Size.S > Size.M) # False
print(Size.L >= Size.M) # True
print(Size.M < 1) # FalseIntFlag
Клас IntFlag можна використовувати в якості базового для перерахувань, члени яких повинні підтримувати побітові операції. Приклад:
from enum import IntFlag
class Role(IntFlag):
OWNER = 8
POWER_USER = 4
USER = 2
ADMIN = OWNER | POWER_USER | USER
person_roles = Role.USER | Role.POWER_USER
print(person_roles) # Role.POWER_USER|USER
print(type(person_roles)) # <enum 'Role'>
print(Role.USER in person_roles) # True
print(Role.OWNER in Role.ADMIN) # TrueЕкземпляри IntFlag також підтримують операції над цілими числами:
print(Role.USER + 1) # 3
print(Role.ADMIN + 5) # 19
print(Role.ADMIN > Role.USER) # TrueFlag
Ще один клас доступний у модулі enum – Flag, подібний до IntFlag, але має деякі обмеження. Основна відмінність від IntFlag полягає в тому, що Flag не успадковує int, тому операції над цілими числами не підтримуються (при спробі буде отримано помилку TypeError):
from enum import Flag, auto
class Permission(Flag):
READ = auto()
WRITE = auto()
EXECUTE = auto()
permission = Permission.READ | Permission.WRITE
print(permission)
# Permission.WRITE|READ
print(Permission.READ > Permission.WRITE)
# TypeError: '>' not supported between instances of 'Permission' and 'Permission'