W3docs

Les enums Python

Apprenez les enums Python : créez Enum, IntEnum, Flag et auto(), ajoutez des méthodes, comparez en toute sécurité et éliminez les nombres magiques de votre code.

Un enum (abréviation d'énumération) est un ensemble de valeurs nommées et constantes regroupées sous un seul type. Au lieu de disperser des entiers ou des chaînes brutes comme 1, 2, "pending", "active" dans votre code, vous donnez à chacun un nom descriptif — Status.PENDING, Color.RED — et Python garantit que ce nom correspond toujours à la même valeur.

Ce chapitre couvre :

  • Pourquoi les enums existent et quels problèmes ils résolvent
  • Créer un enum avec la classe Enum
  • Accéder aux membres par nom et par valeur
  • Itérer sur un enum
  • auto() — laisser Python assigner les valeurs automatiquement
  • IntEnum — des enums qui se comportent comme des entiers
  • Flag — des enums combinables à bits d'indicateur
  • Ajouter des méthodes et des propriétés à un enum
  • Les alias, @unique et _missing_
  • Quand utiliser les enums plutôt que d'autres patterns

Avant de lire ce chapitre, assurez-vous d'être à l'aise avec les classes et objets Python et les types de données Python.

Pourquoi utiliser les enums ?

Considérons cette fonction qui traite un statut de commande passé sous forme d'entier brut :

def handle_order(status):
    if status == 1:
        print("Order is pending")
    elif status == 2:
        print("Order is active")
    elif status == 3:
        print("Order is complete")

Cela fonctionne, mais pose de vrais problèmes :

  • Nombres magiques. Que signifie 2 isolément ? Il faut remonter à la définition de la fonction.
  • Aucune validation. handle_order(99) ne fait rien silencieusement — aucune erreur, aucun avertissement.
  • Les fautes de frappe sont invisibles. handle_order(2) et handle_order(20) sont tous les deux du Python valide.
  • La refactorisation est risquée. Si vous décidez que 1 doit signifier autre chose, vous devez retrouver chaque 1 dans la base de code.

Les enums règlent tous ces problèmes. La même logique écrite avec un enum est auto-documentée, sûre et facile à refactoriser :

from enum import Enum

class OrderStatus(Enum):
    PENDING = 1
    ACTIVE = 2
    COMPLETE = 3

def handle_order(status: OrderStatus):
    if status == OrderStatus.PENDING:
        print("Order is pending")
    elif status == OrderStatus.ACTIVE:
        print("Order is active")
    elif status == OrderStatus.COMPLETE:
        print("Order is complete")

handle_order(OrderStatus.ACTIVE)   # Order is active

L'intention est claire, et Python empêche handle_order(99) de correspondre accidentellement à une branche.

Créer un enum

Importez Enum depuis le module enum (faisant partie de la bibliothèque standard Python — aucune installation nécessaire) et créez une sous-classe :

from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

Chaque attribut de classe (RED, GREEN, BLUE) devient un membre de l'enum. Les valeurs à droite (1, 2, 3) peuvent être des entiers, des chaînes ou tout autre type — le choix vous appartient.

Accéder aux membres

Il existe trois façons d'accéder à un membre d'un enum :

from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

# Attribute access (most common)
print(Color.RED)           # Color.RED

# By name (square bracket notation)
print(Color['GREEN'])      # Color.GREEN

# By value (call the class with the value)
print(Color(3))            # Color.BLUE

Chaque membre expose deux attributs :

print(Color.RED.name)      # RED
print(Color.RED.value)     # 1

Utilisez .name lorsque vous avez besoin d'un libellé lisible par l'humain (pour la journalisation ou l'affichage), et .value lorsque vous devez transmettre la valeur sous-jacente à un système externe (une base de données, une API).

repr et type

print(repr(Color.RED))     # <Color.RED: 1>
print(type(Color.RED))     # <enum 'Color'>

Un membre d'enum est une instance de sa classe enum, pas de int ni de str.

Itérer sur un enum

Les enums sont itérables. L'itération produit les membres dans l'ordre de définition :

from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

for color in Color:
    print(color.name, color.value)
# RED 1
# GREEN 2
# BLUE 3

Vous pouvez également vérifier l'appartenance :

print(Color.RED in Color)   # True

Cela rend les enums pratiques pour remplir des listes déroulantes, construire des tables de dispatch de type switch, ou créer des listes de choix pour la saisie utilisateur.

auto() — Valeurs automatiques

Si les valeurs spécifiques n'ont pas d'importance — vous souhaitez seulement que chaque membre soit distinct — utilisez auto(). Python assigne des entiers séquentiels à partir de 1 :

from enum import Enum, auto

class Direction(Enum):
    NORTH = auto()
    SOUTH = auto()
    EAST = auto()
    WEST = auto()

for d in Direction:
    print(d.name, d.value)
# NORTH 1
# SOUTH 2
# EAST 3
# WEST 4

auto() est particulièrement utile lorsque l'enum sera amené à grandir au fil du temps et que vous ne voulez pas renuméroter manuellement les membres.

Comparer les membres d'un enum

Utilisez is ou == pour comparer les membres. Les deux fonctionnent, mais is est légèrement plus rapide car les membres d'enum sont des singletons — chaque nom correspond exactement à un objet :

from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

print(Color.RED is Color.RED)    # True
print(Color.RED == Color.RED)    # True
print(Color.RED == Color.GREEN)  # False

Un membre Enum ordinaire n'est pas égal à sa valeur brute :

print(Color.RED == 1)   # False

C'est intentionnel. Cela empêche l'égalité accidentelle entre différents enums partageant le même entier :

class Size(Enum):
    SMALL = 1

print(Color.RED == Size.SMALL)   # False — different types

Si vous avez besoin d'une comparaison basée sur la valeur (par exemple, member > 1), utilisez plutôt IntEnum (voir ci-dessous).

IntEnum — Enums qui se comportent comme des entiers

Les membres IntEnum sont également des entiers Python ordinaires. Vous pouvez donc utiliser l'arithmétique, les opérateurs de comparaison et les passer partout où un int est attendu :

from enum import IntEnum

class Priority(IntEnum):
    LOW = 1
    MEDIUM = 2
    HIGH = 3

print(Priority.HIGH > Priority.LOW)    # True
print(Priority.MEDIUM + 10)            # 12
print(Priority.HIGH == 3)              # True

Un cas d'utilisation courant est le tri d'une liste de membres d'enum :

from enum import IntEnum

class Level(IntEnum):
    LOW = 1
    MED = 2
    HIGH = 3

levels = [Level.HIGH, Level.LOW, Level.MED]
print([l.name for l in sorted(levels)])   # ['LOW', 'MED', 'HIGH']

Quand préférer Enum à IntEnum

La transparence entière d'IntEnum est aussi sa faiblesse : Priority.HIGH == 3 est True, donc un littéral mal tapé 3 sera silencieusement égal à Priority.HIGH. Utilisez Enum ordinaire lorsque vous voulez une sûreté de type stricte, et utilisez IntEnum uniquement lorsque vous avez vraiment besoin de l'arithmétique entière ou devez interagir avec une API qui manipule des nombres bruts.

Flag — Enums à bits d'indicateur combinables

Flag est conçu pour les scénarios où plusieurs options peuvent être actives en même temps. Ses membres sont des puissances de deux, et vous les combinez avec l'opérateur | (OU binaire) :

from enum import Flag, auto

class Permission(Flag):
    READ = auto()
    WRITE = auto()
    EXECUTE = auto()
    ALL = READ | WRITE | EXECUTE

user = Permission.READ | Permission.WRITE
print(user)                           # Permission.WRITE|READ
print(Permission.READ in user)        # True
print(Permission.EXECUTE in user)     # False

auto() à l'intérieur de Flag assigne des puissances de deux successives (1, 2, 4, 8, …) afin que la combinaison de membres avec | ne produise jamais de résultats ambigus.

Utilisez Flag pour les systèmes de permissions, les bascules de fonctionnalités, et toute situation où vous avez besoin d'un ensemble compact de commutateurs booléens.

Ajouter des méthodes et des propriétés

Comme un enum est une classe, vous pouvez lui ajouter des méthodes et des propriétés. Cela garde la logique associée à l'intérieur du type plutôt que dispersée dans des chaînes if/elif :

from enum import Enum

class HttpStatus(Enum):
    OK = 200
    CREATED = 201
    NOT_FOUND = 404
    INTERNAL_ERROR = 500

    @property
    def is_success(self):
        return 200 <= self.value < 300

    @property
    def is_error(self):
        return self.value >= 400

def handle_response(status: HttpStatus):
    if status.is_success:
        print(f"{status.value} {status.name}: request succeeded")
    elif status.is_error:
        print(f"{status.value} {status.name}: request failed")

handle_response(HttpStatus.OK)           # 200 OK: request succeeded
handle_response(HttpStatus.NOT_FOUND)    # 404 NOT_FOUND: request failed

Vous pouvez également donner à un enum un __init__ personnalisé pour stocker des données supplémentaires par membre. Fournissez les valeurs sous forme de tuples :

from enum import 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(round(Planet.EARTH.surface_gravity, 2))    # 9.8
print(round(Planet.MERCURY.surface_gravity, 2))  # 3.7

Le tuple (mass, radius) devient les arguments du constructeur ; self.value contient toujours le tuple complet.

Alias et @unique

Si deux membres partagent la même valeur, le second devient un alias — il se résout vers le premier membre. Les alias ne sont pas produits lors de l'itération :

from enum import Enum

class Status(Enum):
    ACTIVE = 1
    RUNNING = 1   # alias for ACTIVE

print(Status.ACTIVE is Status.RUNNING)   # True
print(list(Status))                      # [<Status.ACTIVE: 1>]

Les alias sont parfois utiles (par exemple, un ancien nom pointant vers un nouveau), mais ils peuvent aussi masquer des fautes de frappe. Appliquez le décorateur @unique pour interdire complètement les valeurs dupliquées :

from enum import Enum, unique

@unique
class Status(Enum):
    PENDING = 1
    ACTIVE = 2
    INACTIVE = 3

# Trying to add a duplicate value to a @unique enum raises ValueError:
# ValueError: duplicate values found in <enum 'Bad'>: B -> A

@unique est un bon choix par défaut pour tout enum où un alias accidentel constituerait un bug.

Recherche personnalisée avec _missing_

Par défaut, appeler Color('unknown') lève une ValueError. Vous pouvez surcharger la méthode de classe _missing_ pour gérer les valeurs non reconnues — par exemple, pour effectuer une recherche insensible à la casse :

from enum import Enum

class Color(Enum):
    RED = 'red'
    GREEN = 'green'
    BLUE = 'blue'

    @classmethod
    def _missing_(cls, value):
        if isinstance(value, str):
            for member in cls:
                if member.value == value.lower():
                    return member
        return None

print(Color('RED'))     # Color.RED
print(Color('Green'))   # Color.GREEN

_missing_ reçoit la valeur qui n'a pas été trouvée. Retournez le membre correspondant, ou None (ce qui laisse Python lever sa ValueError par défaut).

Quand utiliser les enums

Les enums sont le bon choix lorsque :

  • Une variable ne peut contenir qu'un ensemble fixe d'états nommés (statut de commande, verbe HTTP, couleur de carte).
  • Vous voulez empêcher les valeurs invalides de passer silencieusement.
  • Le même concept est comparé en plusieurs endroits et vous souhaitez une source de vérité unique.
  • Vous avez besoin d'itérer sur toutes les valeurs valides (remplir un formulaire, documenter une API).

Vous n'avez probablement pas besoin d'un enum lorsque :

  • L'ensemble de valeurs est ouvert ou change au moment de l'exécution (utilisez un dictionnaire ou une table de correspondance en base de données).
  • Vous n'avez besoin que de deux étatsTrue/False avec une signification booléenne claire est plus simple.
  • Les valeurs proviennent d'une saisie utilisateur qui doit être validée par rapport à un schéma — envisagez une bibliothèque comme Pydantic, qui s'intègre parfaitement avec les enums Python.

Pour des patterns étroitement liés, consultez les dataclasses Python (pour les données structurées avec des valeurs par défaut) et les classes abstraites Python (pour imposer des contrats d'interface entre les sous-classes). Si vous avez besoin de conteneurs de constantes nommées sans toute la machinerie des enums, le module collections Python propose namedtuple comme alternative.

Pratique

Pratique
What does Color['RED'] do when Color is an Enum with a RED member?
What does Color['RED'] do when Color is an Enum with a RED member?
Was this page helpful?