Enum в Python: подробный разбор с примерами кода и практическими сценариями
Перечисляемый тип данных (enum) в Python
Перечисляемый тип данных в Python позволяет создавать набор именованных констант. Это удобно для ограничения возможных значений переменной и улучшения читаемости кода. Встроенный модуль enum предоставляет несколько классов для работы с перечислениями.
Как создать базовое перечисление с уникальными значениями?
Самый распространенный способ - наследовать от класса Enum и задать атрибуты класса. Каждый атрибут становится членом перечисления со значением по умолчанию (целые числа, начиная с 1).
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
print(Color.RED) # Color.RED
print(Color.RED.value) # 1
print(Color.RED.name) # 'RED'перечисляемый тип данных python (перечисляемый тип данных (enum) в python)
Каждый член является экземпляром класса перечисления. Два одинаковых значения (например, RED = 1 и ORANGE = 1) приведут к ошибке ValueError: duplicate values. Если нужны синонимы, можно использовать @enum.unique или определить псевдонимы через присваивание (тогда псевдоним будет ссылаться на тот же объект).
Проблема:
Повторяющиеся значения без @unique не запрещены, но создают псевдонимы, что может вызвать путаницу. Решение - использовать декоратор @enum.unique для гарантии уникальности.
from enum import Enum, unique
@unique
class Status(Enum):
OK = 200
NOT_FOUND = 404
# ERROR = 200 # ValueError: duplicate valueКак создать перечисление с целыми числами, совместимое с целочисленными операциями?
Класс IntEnum наследует от int и Enum. Члены такого перечисления можно использовать как обычные целые числа.
from enum import IntEnum
class HttpStatus(IntEnum):
OK = 200
NOT_FOUND = 404
code = 200
print(code == HttpStatus.OK) # True
print(HttpStatus.NOT_FOUND + 1) # 405Как создать перечисление со строковыми значениями?
Класс StrEnum (доступен с Python 3.11) наследует от str и Enum. Позволяет использовать члены как строки.
from enum import StrEnum
class LogLevel(StrEnum):
DEBUG = 'debug'
INFO = 'info'
ERROR = 'error'
print(LogLevel.INFO.upper()) # 'INFO'Как создать флаговые перечисления для битовых масок?
Класс Flag поддерживает битовые операции: |, &, ~. Члены должны быть степенями двойки. Удобно использовать auto() для автоматической нумерации.
from enum import Flag, auto
class Permission(Flag):
READ = auto() # 1
WRITE = auto() # 2
EXECUTE = auto() # 4
perm = Permission.READ | Permission.WRITE
print(perm) # Permission.READ|WRITE
print(Permission.READ in perm) # TrueКак автоматически присвоить значения членам перечисления?
Функция auto() генерирует значения автоматически. Поведение зависит от базового класса: для Enum - целые числа с шагом 1, для Flag - степени двойки.
from enum import Enum, auto
class Weekday(Enum):
MONDAY = auto()
TUESDAY = auto()
WEDNESDAY = auto()
print(list(Weekday))
# [<Weekday.MONDAY: 1>, <Weekday.TUESDAY: 2>, <Weekday.WEDNESDAY: 3>]Проблема:
Если нужно задать пользовательские значения после auto(), порядок может нарушиться. Решение - задать все значения явно или переопределить функцию _generate_next_value_.
Как получить все члены перечисления?
Итерация по классу перечисления возвращает все члены в порядке их объявления.
for member in Color:
print(member.name, member.value)
# RED 1
# GREEN 2
# BLUE 3Как проверить принадлежность значения перечислению?
Оператор is или == (для разных типов - is надёжнее). Можно использовать isinstance().
c = Color.RED
print(c is Color.RED) # True
print(isinstance(c, Color)) # TrueДля проверки по значению используется конструктор: Color(1) возвращает Color.RED, если значение существует, иначе ValueError. Либо Color['RED'] по имени.
print(Color(2)) # Color.GREEN
print(Color['BLUE']) # Color.BLUEПроблема:
При использовании Color(2) с несуществующим значением возникает ValueError. Решение - обернуть в try/except или использовать _value2member_map_.
Расширенные примеры работы с enum
from enum import Enum, auto, unique, Flag, IntFlag, member, nonmember, property
# 1. Кастомные методы в enum
class Planet(Enum):
MERCURY = (3.303e+23, 2.4397e6)
VENUS = (4.869e+24, 6.0518e6)
EARTH = (5.976e+24, 6.37814e6)
def __init__(self, mass, radius):
self.mass = mass
self.radius = radius
@property
def surface_gravity(self):
G = 6.67430e-11
return G * self.mass / (self.radius ** 2)
print(Planet.EARTH.surface_gravity) # 9.802...
# 2. Использование декораторов @member и @nonmember для управления атрибутами
class StatusAdvanced(Enum):
PENDING = 'pending'
PROCESSING = 'processing'
COMPLETED = 'completed'
@nonmember
def is_final(self):
return self in (StatusAdvanced.COMPLETED,)
print(StatusAdvanced.PENDING.is_final()) # False
# 3. Динамическое создание перечисления
names = ['APPLE', 'BANANA', 'CHERRY']
Fruits = Enum('Fruits', names)
print(list(Fruits)) # [<Fruits.APPLE: 1>, <Fruits.BANANA: 2>, <Fruits.CHERRY: 3>]
# 4. Использование IntFlag для битовых масок с операциями
class Permissions(IntFlag):
READ = 1
WRITE = 2
EXECUTE = 4
ALL = READ | WRITE | EXECUTE
user_perm = Permissions.READ | Permissions.WRITE
print(user_perm & Permissions.EXECUTE) # <Permissions.0: 0> (нет execute)
print(user_perm ^ Permissions.ALL) # <Permissions.EXECUTE: 4>
# 5. Сравнение и хэширование
from enum import Enum
class OrderStatus(Enum):
NEW = 0
PAID = 1
SHIPPED = 2
s = {OrderStatus.NEW, OrderStatus.PAID}
print(OrderStatus.NEW in s) # True
# 6. Сериализация через JSON (используя custom encoder)
import json
class StatusEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Enum):
return obj.value
return super().default(obj)
data = {'status': StatusAdvanced.PENDING}
print(json.dumps(data, cls=StatusEncoder)) # {"status": "pending"}
# 7. Обратная сериализация
pending_value = 'pending'
status = StatusAdvanced(pending_value)
print(status) # StatusAdvanced.PENDING
# 8. Использование __members__ для получения словаря
print(StatusAdvanced.__members__)
# {'PENDING': <StatusAdvanced.PENDING: 'pending'>, ...}Результат выполнения кода (выборочно):
1. 9.802...
2. False
3. [<Fruits.APPLE: 1>, <Fruits.BANANA: 2>, <Fruits.CHERRY: 3>]
4. <Permissions.0: 0>
<Permissions.EXECUTE: 4>
5. True
6. {"status": "pending"}
7. StatusAdvanced.PENDING
8. {'PENDING': <StatusAdvanced.PENDING: 'pending'>, 'PROCESSING': <StatusAdvanced.PROCESSING: 'processing'>, 'COMPLETED': <StatusAdvanced.COMPLETED: 'completed'>}Типичные ошибки в расширенных сценариях:
- Попытка наследовать от Enum и другого класса (кроме int, str) приводит к TypeError. Решение - использовать mixin'ы через
class MyEnum(int, Enum). - В IntFlag нельзя комбинировать флаги из разных перечислений. Решение - использовать только члены одного класса.
- При сериализации в JSON enum-объекты не поддерживаются по умолчанию. Решение - свой encoder или метод
.value.