W3docs

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 automaticamente
  • IntEnum — enum che si comportano come interi
  • Flag — enum a bit-flag combinabili
  • Aggiungere metodi e proprietà a un enum
  • Alias, @unique e _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 2 isolato? 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) e handle_order(20) sono entrambi Python valido.
  • Il refactoring è rischioso. Se decidi che 1 deve significare qualcos'altro, devi trovare ogni 1 nella 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 active

L'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 = 3

Ogni 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.BLUE

Ogni membro espone due attributi:

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

Usa .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 3

Puoi anche verificare l'appartenenza:

print(Color.RED in Color)   # True

Questo 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 4

auto() è 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)  # False

Un membro Enum semplice non è uguale al suo valore grezzo:

print(Color.RED == 1)   # False

Questo è 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 types

Se 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)              # True

Un 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)     # False

auto() 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 failed

Puoi 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.7

La 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 statiTrue/False con 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.

Esercitazione

Pratica
Cosa fa Color['RED'] quando Color è un Enum con un membro RED?
Cosa fa Color['RED'] quando Color è un Enum con un membro RED?
Was this page helpful?