Enum in Python
Scopri gli enum in Python: crea Enum, IntEnum, Flag e auto(), aggiungi metodi, confronta in sicurezza ed elimina i magic number dal tuo codice.
Un enum (abbreviazione di enumerazione) è un insieme di valori costanti con nome, raggruppati sotto un unico tipo. Invece di disseminare interi o stringhe bare come 1, 2, "pending", "active" nel codice, assegni a ciascuno un nome descrittivo — Status.PENDING, Color.RED — e Python garantisce che quel nome punti sempre allo stesso valore.
Questo capitolo tratta:
- Perché esistono gli enum e quali problemi risolvono
- Creare un enum con la classe
Enum - Accedere ai membri per nome e per valore
- Iterare su un enum
auto()— lasciare che Python assegni i valori automaticamenteIntEnum— enum che si comportano come interiFlag— enum a bit-flag combinabili- Aggiungere metodi e proprietà a un enum
- Alias,
@uniquee_missing_ - Quando usare gli enum rispetto ad altri pattern
Prima di leggere questo capitolo, assicurati di avere familiarità con classi e oggetti Python e i tipi di dati Python.
Perché usare gli enum?
Considera questa funzione che elabora lo stato di un ordine passato come semplice intero:
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")Funziona, ma presenta problemi reali:
- Magic number. Cosa significa
2isolato? Devi risalire alla definizione della funzione. - Nessuna validazione.
handle_order(99)non fa nulla in silenzio — nessun errore, nessun avviso. - I refusi sono invisibili.
handle_order(2)ehandle_order(20)sono entrambi Python valido. - Il refactoring è rischioso. Se decidi che
1deve significare qualcos'altro, devi trovare ogni1nella codebase.
Gli enum risolvono tutto ciò. La stessa logica scritta con un enum è auto-documentante, sicura e predisposta al refactoring:
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 activeL'intento è chiaro, e Python impedisce a handle_order(99) di corrispondere accidentalmente a qualsiasi ramo.
Creare un enum
Importa Enum dal modulo enum (parte della libreria standard di Python — nessuna installazione necessaria) e crea una sua sottoclasse:
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3Ogni attributo a livello di classe (RED, GREEN, BLUE) diventa un membro dell'enum. I valori a destra (1, 2, 3) possono essere interi, stringhe o qualsiasi altro tipo — la scelta spetta a te.
Accedere ai membri
Ci sono tre modi per raggiungere un membro dell'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.BLUEOgni membro espone due attributi:
print(Color.RED.name) # RED
print(Color.RED.value) # 1Usa .name quando hai bisogno di un'etichetta leggibile dall'utente (per log o visualizzazione), e .value quando devi passare il valore sottostante a un sistema esterno (un database, una API).
repr e type
print(repr(Color.RED)) # <Color.RED: 1>
print(type(Color.RED)) # <enum 'Color'>Un membro di un enum è un'istanza della propria classe enum, non di int o str.
Iterare su un enum
Gli enum sono iterabili. L'iterazione restituisce i membri nell'ordine di definizione:
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 3Puoi anche verificare l'appartenenza:
print(Color.RED in Color) # TrueQuesto rende gli enum utili per popolare menu a tendina, creare tabelle di dispatch simili a switch o costruire elenchi di scelte per l'input utente.
auto() — Valori automatici
Se i valori specifici non contano — ti interessa solo che ogni membro sia distinto — usa auto(). Python assegna interi sequenziali a partire da 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 4auto() è particolarmente utile quando l'enum crescerà nel tempo e non vuoi rinumerare manualmente i membri.
Confrontare i membri di un enum
Usa is o == per confrontare i membri. Entrambi funzionano, ma is è leggermente più veloce perché i membri degli enum sono singleton — ogni nome corrisponde esattamente a un oggetto:
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) # FalseUn membro Enum semplice non è uguale al suo valore grezzo:
print(Color.RED == 1) # FalseQuesto è intenzionale. Previene l'uguaglianza accidentale tra enum diversi che condividono lo stesso intero:
class Size(Enum):
SMALL = 1
print(Color.RED == Size.SMALL) # False — different typesSe hai bisogno di un confronto basato sul valore (es. member > 1), usa invece IntEnum (vedi sotto).
IntEnum — Enum che si comportano come interi
I membri di IntEnum sono anche interi Python normali. Puoi quindi usare aritmetica, operatori di confronto e passarli ovunque sia atteso un int:
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) # TrueUn caso d'uso comune è l'ordinamento di una lista di membri 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']Quando preferire Enum a IntEnum
La trasparenza intera di IntEnum è anche il suo punto debole: Priority.HIGH == 3 è True, quindi un letterale 3 digitato per errore sarà silenziosamente uguale a Priority.HIGH. Usa Enum semplice quando vuoi una rigorosa sicurezza di tipo, e usa IntEnum solo quando hai genuinamente bisogno dell'aritmetica intera o devi interfacciarti con una API che lavora con numeri grezzi.
Flag — Enum a bit-flag combinabili
Flag è progettato per scenari in cui più opzioni possono essere attive contemporaneamente. I suoi membri sono potenze di due, e li combini con l'operatore | (OR bit a bit):
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) # Falseauto() all'interno di Flag assegna successive potenze di due (1, 2, 4, 8, …) in modo che la combinazione di membri con | non produca mai risultati ambigui.
Usa Flag per sistemi di permessi, feature toggle e qualsiasi situazione in cui hai bisogno di un insieme compatto di interruttori boolean.
Aggiungere metodi e proprietà
Poiché un enum è una classe, puoi aggiungere metodi e proprietà. Questo mantiene la logica correlata all'interno del tipo anziché disseminata in catene 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 failedPuoi anche fornire a un enum un __init__ personalizzato per memorizzare dati aggiuntivi per ogni membro. Fornisci i valori come tuple:
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.7La tupla (mass, radius) diventa gli argomenti del costruttore; self.value contiene ancora la tupla completa.
Alias e @unique
Se due membri condividono lo stesso valore, il secondo diventa un alias — viene risolto al primo membro. Gli alias non vengono restituiti durante l'iterazione:
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>]Gli alias sono talvolta utili (es. un nome legacy che punta a uno nuovo), ma possono anche mascherare refusi. Applica il decoratore @unique per vietare del tutto i valori duplicati:
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 è una buona impostazione predefinita per qualsiasi enum in cui l'aliasing accidentale sarebbe un bug.
Ricerca personalizzata con _missing_
Per impostazione predefinita, chiamare Color('unknown') genera un ValueError. Puoi sovrascrivere il metodo di classe _missing_ per gestire valori non riconosciuti — ad esempio, per eseguire una ricerca senza distinzione tra maiuscole e minuscole:
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_ riceve il valore non trovato. Restituisce il membro corrispondente, o None (che consente a Python di sollevare il suo ValueError predefinito).
Quando usare gli enum
Gli enum sono la scelta giusta quando:
- Una variabile può contenere solo uno di un insieme fisso di stati con nome (stato dell'ordine, verbo HTTP, seme della carta).
- Vuoi impedire che valori non validi passino silenziosamente.
- Lo stesso concetto viene confrontato in più punti e vuoi un'unica fonte di verità.
- Hai bisogno di iterare su tutti i valori validi (popolando un modulo, documentando una API).
Probabilmente non hai bisogno di un enum quando:
- L'insieme di valori è aperto o cambia a runtime (usa un dizionario o una tabella di lookup del database).
- Hai solo bisogno di due stati —
True/Falsecon un chiaro significato boolean è più semplice. - I valori provengono da input utente che deve essere validato rispetto a uno schema — considera una libreria come Pydantic, che si integra perfettamente con gli enum Python.
Per pattern strettamente correlati, vedi Python dataclasses (per dati strutturati con valori predefiniti) e classi astratte Python (per applicare contratti di interfaccia tra sottoclassi). Se hai bisogno di contenitori di costanti con nome senza tutta la struttura degli enum, il modulo collections di Python offre namedtuple come alternativa.