Aller au contenu
Développement medium

Python et les énumérations : enum, Flag, StrEnum

15 min de lecture

logo python

Une énumération est un ensemble fixe de valeurs nommées et constantes, regroupées dans une seule classe. Au lieu de disperser des constantes magiques ("publie", 200, 0644) dans votre code, vous les rassemblez sous un type unique, lisible et vérifiable. Le module enum de la bibliothèque standard fournit tout le nécessaire : la classe de base Enum, les variantes typées IntEnum et StrEnum, les indicateurs de bits Flag et IntFlag, plus les outils auto() et @unique. Ce guide couvre chacun de ces éléments avec du code exécutable.

Il s'adresse aux développeurs qui manipulent des statuts, des codes de retour, des niveaux de priorité ou des combinaisons d'options, et qui veulent remplacer leurs chaînes ou entiers en dur par des valeurs robustes. Les exemples ont été exécutés avec Python 3.12.

  • Créer une énumération avec enum.Enum et lire ses membres via .name et .value.
  • Itérer, comparer et accéder aux membres par nom ou par valeur.
  • Générer des valeurs avec auto() et garantir l'unicité avec @unique.
  • Choisir entre IntEnum, StrEnum, Flag et IntFlag selon le besoin.
  • Combiner des options par opérations sur les bits et ajouter des méthodes à une énumération.
  • Décider quand une énumération vaut mieux que de simples constantes.

Une énumération (souvent abrégée enum) est une classe qui hérite de enum.Enum et dont chaque attribut devient un membre constant. Chaque membre possède deux propriétés : son nom (.name, la chaîne de gauche) et sa valeur (.value, l'objet de droite). L'exemple ci-dessous modélise le statut d'un article de blog.

from enum import Enum
class Statut(Enum):
BROUILLON = "brouillon"
PUBLIE = "publie"
ARCHIVE = "archive"
print(Statut.PUBLIE) # Statut.PUBLIE
print(Statut.PUBLIE.name) # PUBLIE
print(Statut.PUBLIE.value) # publie
print(repr(Statut.PUBLIE)) # <Statut.PUBLIE: 'publie'>

Chaque membre est une instance unique de la classe, créée une seule fois au chargement. Ce point est fondamental : deux références au même membre pointent vers le même objet en mémoire, ce qui rend la comparaison rapide et sûre.

Un membre d'énumération se récupère de trois façons : par attribut (Statut.PUBLIE), par valeur avec un appel (Statut("publie")), ou par nom avec des crochets (Statut["PUBLIE"]). La forme par valeur sert typiquement à reconstruire un membre depuis une donnée lue en base ou dans un fichier JSON.

print(Statut("publie")) # Statut.PUBLIE (recherche par valeur)
print(Statut["PUBLIE"]) # Statut.PUBLIE (recherche par nom)

Comme chaque membre est un singleton, la comparaison d'identité is est la manière idiomatique de tester un statut. L'égalité == fonctionne aussi, mais is exprime clairement qu'on compare des constantes.

print(Statut.PUBLIE is Statut.PUBLIE) # True
print(Statut.PUBLIE == Statut.ARCHIVE) # False

Une énumération est itérable dans l'ordre de déclaration. Cette propriété est précieuse pour remplir un menu déroulant, générer une documentation ou construire une liste de choix dans un formulaire.

for statut in Statut:
print(statut.name, "->", statut.value)
BROUILLON -> brouillon
PUBLIE -> publie
ARCHIVE -> archive

Le dictionnaire spécial __members__ expose la correspondance nom vers membre, et une simple compréhension de liste extrait toutes les valeurs. Ces deux formes couvrent la quasi-totalité des besoins de parcours.

print([s.value for s in Statut]) # ['brouillon', 'publie', 'archive']
print(list(Statut.__members__)) # ['BROUILLON', 'PUBLIE', 'ARCHIVE']

Quand la valeur exacte des membres n'a pas d'importance et que seul leur nom compte, écrire 1, 2, 3 à la main est fastidieux et source d'erreurs. La fonction auto() attribue automatiquement une valeur entière croissante à partir de 1.

from enum import Enum, auto
class Priorite(Enum):
BASSE = auto()
MOYENNE = auto()
HAUTE = auto()
for p in Priorite:
print(p.name, p.value)
BASSE 1
MOYENNE 2
HAUTE 3

Utilisez auto() lorsque les valeurs sont un détail d'implémentation. Si au contraire la valeur porte un sens métier (un code HTTP, un libellé stocké en base), écrivez-la explicitement pour ne pas dépendre de l'ordre des lignes.

Par défaut, Python autorise plusieurs noms pour une même valeur : le second devient un alias du premier. C'est parfois voulu, mais souvent c'est un bug de copier-coller. Le décorateur @unique interdit ce cas et lève une erreur au chargement du module, donc avant l'exécution du reste du programme.

from enum import Enum, unique
@unique
class Feu(Enum):
ROUGE = 1
ORANGE = 2
VERT = 3

Si deux membres partagent une valeur sous @unique, l'import échoue avec un message explicite. Cette vérification précoce évite qu'un doublon silencieux ne se propage.

@unique
class FeuCasse(Enum):
ROUGE = 1
STOP = 1 # meme valeur que ROUGE
# ValueError: duplicate values found in <enum 'FeuCasse'>: STOP -> ROUGE

IntEnum : une énumération qui est aussi un entier

Section intitulée « IntEnum : une énumération qui est aussi un entier »

Une classe qui hérite de IntEnum produit des membres qui sont des entiers à part entière. Ils s'additionnent, se comparent et se trient comme des int, tout en gardant un nom lisible. C'est le bon choix pour des codes HTTP, des codes de sortie ou tout ce qui doit rester numériquement compatible avec du code existant.

from enum import IntEnum
class CodeHTTP(IntEnum):
OK = 200
NON_TROUVE = 404
ERREUR_SERVEUR = 500
print(CodeHTTP.OK == 200) # True
print(CodeHTTP.ERREUR_SERVEUR >= 500) # True
print(sorted([CodeHTTP.ERREUR_SERVEUR, CodeHTTP.OK, CodeHTTP.NON_TROUVE]))
[<CodeHTTP.OK: 200>, <CodeHTTP.NON_TROUVE: 404>, <CodeHTTP.ERREUR_SERVEUR: 500>]

La contrepartie de cette souplesse est une frontière de type plus floue : un IntEnum se compare sans broncher à n'importe quel entier. Sur une énumération classique, Statut.PUBLIE == 1 renvoie False sans erreur, ce qui protège mieux contre les confusions. Réservez IntEnum aux cas où la compatibilité numérique est réellement utile.

Les classes Flag et IntFlag sont conçues pour des membres que l'on combine, à la manière des permissions d'un fichier Linux. Chaque membre reçoit une puissance de deux (via auto()), ce qui permet de les assembler avec l'opérateur OU binaire | et de tester leur présence avec le ET binaire & ou l'opérateur in.

from enum import Flag, auto
class Permission(Flag):
LECTURE = auto()
ECRITURE = auto()
EXECUTION = auto()
acces = Permission.LECTURE | Permission.ECRITURE
print(acces) # Permission.LECTURE|ECRITURE
print(Permission.ECRITURE in acces) # True
print(Permission.EXECUTION in acces) # False
print(bool(acces & Permission.LECTURE)) # True

Une combinaison de Flag reste itérable : la parcourir énumère les membres réellement activés, ce qui simplifie l'affichage ou la journalisation d'un ensemble de droits.

for perm in acces:
print("actif:", perm.name)
actif: LECTURE
actif: ECRITURE

La variante IntFlag ajoute la compatibilité entière : la combinaison se convertit en int et expose sa valeur numérique, utile quand un masque de bits doit voyager vers une API ou un système externe.

from enum import IntFlag
class Options(IntFlag):
NONE = 0
VERBEUX = 1
COULEUR = 2
FORCE = 4
combo = Options.VERBEUX | Options.FORCE
print(int(combo)) # 5
print(combo) # 5

StrEnum : une énumération qui est aussi une chaîne

Section intitulée « StrEnum : une énumération qui est aussi une chaîne »

Depuis Python 3.11, la classe StrEnum joue pour les chaînes le rôle que IntEnum tient pour les entiers : ses membres sont des str. Ils se concatènent, se mettent en majuscules et se comparent directement à des chaînes, tout en conservant l'intérêt d'un type nommé. C'est pratique pour des noms d'environnement, des clés de configuration ou des libellés sérialisés en JSON.

from enum import StrEnum
class Environnement(StrEnum):
DEV = "developpement"
STAGING = "staging"
PROD = "production"
print(Environnement.PROD) # production
print(Environnement.PROD == "production") # True
print("prefixe-" + Environnement.DEV) # prefixe-developpement
print(Environnement.PROD.upper()) # PRODUCTION

Notez qu'un StrEnum s'affiche comme sa valeur brute (production) et non Environnement.PROD, contrairement à une Enum classique. C'est ce qui rend l'insertion dans une chaîne ou une réponse HTTP transparente.

Une énumération reste une classe : elle peut porter des méthodes, des propriétés et même un constructeur __init__ pour décomposer une valeur composite en plusieurs attributs. Cela transforme l'énumération en une petite table de données comportementale, sans dépendre d'un dictionnaire externe.

L'exemple ci-dessous stocke pour chaque planète sa masse et son rayon, puis calcule la gravité de surface et le poids d'un objet à partir de ces attributs.

from enum import Enum
class Planete(Enum):
MERCURE = (3.303e23, 2.4397e6)
TERRE = (5.976e24, 6.37814e6)
def __init__(self, masse, rayon):
self.masse = masse
self.rayon = rayon
@property
def gravite(self):
G = 6.67300e-11
return G * self.masse / (self.rayon * self.rayon)
def poids(self, masse_kg):
return masse_kg * self.gravite
print(round(Planete.TERRE.gravite, 2)) # 9.8
print(round(Planete.TERRE.poids(80), 1)) # 784.2
print(round(Planete.MERCURE.gravite, 2)) # 3.7

Une méthode plus simple encapsule une règle métier directement sur l'énumération, ce qui évite de dupliquer des conditions un peu partout dans le code.

class Jour(Enum):
LUNDI = 1
SAMEDI = 6
DIMANCHE = 7
def est_weekend(self):
return self in (Jour.SAMEDI, Jour.DIMANCHE)
print(Jour.LUNDI.est_weekend()) # False
print(Jour.DIMANCHE.est_weekend()) # True

Quand préférer une énumération à des constantes

Section intitulée « Quand préférer une énumération à des constantes »

Le réflexe naturel du débutant est d'écrire des constantes de module (STATUT_PUBLIE = "publie") ou pire, des chaînes en dur dispersées dans le code. Une énumération apporte des garanties qu'aucune de ces approches ne fournit, et le tableau suivant résume ce que vous gagnez.

Sans énumérationAvec énumération
Faute de frappe ("publei") silencieuseStatut["PUBLIE"] lève KeyError
Aucune liste des valeurs possiblesItération native sur les membres
Pas de type dédié pour les annotationsdef publier() -> Statut typable
Regroupement implicite, fragileUn seul point de vérité, une classe

Choisissez une énumération dès qu'un ensemble de valeurs est fermé et connu à l'avance : statuts, rôles, niveaux, codes, options. Un tel groupe gagne à être nommé, itérable et vérifié. Gardez en revanche de simples constantes pour une valeur isolée sans famille (une limite de taille, une URL de base) : les envelopper dans une énumération d'un seul membre n'apporterait rien.

Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

  • Une énumération hérite de enum.Enum ; chaque membre expose son .name et sa .value.
  • Chaque membre est un singleton : comparez avec is, accédez par valeur Statut("publie") ou par nom Statut["PUBLIE"].
  • Une énumération est itérable dans l'ordre de déclaration ; __members__ liste noms et membres.
  • auto() génère les valeurs quand elles n'ont pas de sens métier ; @unique interdit les doublons dès l'import.
  • IntEnum et StrEnum produisent des membres qui sont des int ou des str ; pratique mais typage plus lâche.
  • Flag et IntFlag combinent des options par bits avec | et & ; une combinaison reste itérable.
  • Une énumération peut porter des méthodes, des propriétés et un __init__ pour des attributs multiples.
  • Préférez une énumération dès qu'un ensemble de valeurs est fermé et connu ; gardez des constantes pour les valeurs isolées.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn