Enum в Python: подробный разбор с примерами кода и практическими сценариями

Раздел: Типы данных -> Enum

Перечисляемый тип данных (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.

Перечисляемый тип данных (enum) в Python - comments

En
перечисляемый тип данных python (python)