W3docs

Python raise ed Eccezioni Personalizzate

Impara a usare l'istruzione raise di Python, il concatenamento di eccezioni con raise...from e la creazione di classi di eccezione personalizzate.

Python ti permette di fare molto più che intercettare gli errori — puoi anche segnalarli deliberatamente con l'istruzione raise e creare i tuoi tipi di eccezione per rappresentare problemi specifici del dominio. Questo capitolo si basa su Python Try...Except e tratta:

  • L'istruzione raise — sollevare eccezioni built-in
  • Risollevare eccezioni all'interno di un blocco except
  • Il concatenamento di eccezioni con raise ... from
  • Creare classi di eccezione personalizzate
  • Costruire una gerarchia di eccezioni per un'applicazione reale
  • L'istruzione assert e quando usarla

L'istruzione raise

L'istruzione raise ti consente di lanciare un'eccezione in qualsiasi punto del codice. La forma più comune passa un'istanza di eccezione con un messaggio descrittivo:

raise ExceptionType("message")

Usa raise quando il tuo codice rileva un problema che il chiamante deve gestire. Ad esempio, una funzione che accetta un'età dovrebbe rifiutare immediatamente i valori negativi invece di proseguire silenziosamente:

def set_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative")
    return age

try:
    set_age(-1)
except ValueError as e:
    print(e)
# Output: Age cannot be negative

Scegliere la Giusta Eccezione Built-in

I tipi di eccezione built-in di Python trasmettono un significato preciso. Scegliere quello corretto rende la tua API più comprensibile e consente ai chiamanti di gestire separatamente le diverse categorie di errore.

EccezioneQuando sollevarla
ValueErrorL'argomento ha il tipo corretto ma un valore non valido (age = -1)
TypeErrorL'argomento ha il tipo sbagliato (age = "old")
KeyErrorManca una chiave obbligatoria del dizionario
IndexErrorL'indice di una sequenza è fuori intervallo
FileNotFoundErrorUn file richiesto non esiste
PermissionErrorIl processo non ha i diritti per eseguire un'operazione
RuntimeErrorUn problema generico di runtime che non rientra in un tipo più specifico
NotImplementedErrorUn metodo esiste in una classe base ma deve essere sovrascritto

Sollevare ValueError per un valore errato è molto più informativo che sollevare una generica Exception, perché i chiamanti possono scrivere except ValueError per gestire esattamente quel caso.

Risollevare un'Eccezione

A volte vuoi fare qualcosa quando si verifica un'eccezione — registrarla, liberare una risorsa — e poi lasciare che la stessa eccezione si propaghi al chiamante senza modifiche. Chiama raise senza argomenti all'interno di un blocco except per risollevare l'eccezione corrente:

def read_config(path):
    try:
        with open(path) as f:
            return f.read()
    except FileNotFoundError:
        print(f"Warning: config file not found at {path}")
        raise  # re-raise the original FileNotFoundError

try:
    read_config("missing.cfg")
except FileNotFoundError as e:
    print(f"Caught: {e}")
# Output:
# Warning: config file not found at missing.cfg
# Caught: [Errno 2] No such file or directory: 'missing.cfg'

Usare raise senza argomenti preserva il traceback originale, il che rende il debug molto più semplice rispetto all'intercettare e risollevare e come una nuova eccezione.

Concatenamento di Eccezioni con raise ... from

Quando intercetti un'eccezione e ne sollevi un'altra differente, Python registra automaticamente l'eccezione originale come contesto di quella nuova. Puoi rendere questa relazione esplicita — e significativa — usando raise NewException from original:

def load_data(path):
    try:
        with open(path) as f:
            return f.read()
    except OSError as e:
        raise RuntimeError("Failed to load configuration") from e

try:
    load_data("config.json")
except RuntimeError as e:
    print(f"Error: {e}")
    print(f"Caused by: {e.__cause__}")
# Output:
# Error: Failed to load configuration
# Caused by: [Errno 2] No such file or directory: 'config.json'

Quando Python stampa il traceback mostra entrambe le eccezioni in ordine, rendendo chiaro che il RuntimeError era una diretta conseguenza dell'OSError. Questo è particolarmente utile nel codice di libreria, dove vuoi tradurre errori OS di basso livello in errori di dominio di livello superiore senza nascondere la causa principale.

Sopprimere la Catena con raise ... from None

A volte l'eccezione originale è un dettaglio implementativo che non vuoi esporre. Passa None come causa per nasconderla:

def fetch(url):
    try:
        raise ConnectionError("timeout")
    except ConnectionError:
        raise RuntimeError("Network unavailable") from None

try:
    fetch("http://example.com")
except RuntimeError as e:
    print(f"Error: {e}")
    print(f"Cause hidden: {e.__cause__}")
# Output:
# Error: Network unavailable
# Cause hidden: None

Il traceback mostrerà solo il RuntimeError. Usa questa tecnica con parsimonia — nascondere la causa principale rende il debug più difficile per i consumatori della libreria.

Creare Classi di Eccezione Personalizzate

Le eccezioni built-in coprono gli errori di programmazione comuni, ma sono troppo generiche per i problemi di dominio. Se la tua applicazione di e-commerce solleva un semplice ValueError quando un pagamento fallisce, i chiamanti non possono distinguerlo da un argomento di funzione non valido. Le classi di eccezione personalizzate risolvono questo problema.

Un'eccezione personalizzata è semplicemente una classe che eredita da Exception (o da una delle sue sottoclassi):

class InsufficientFundsError(Exception):
    """Raised when a bank account has insufficient funds."""
    def __init__(self, amount, balance):
        self.amount = amount
        self.balance = balance
        super().__init__(
            f"Cannot withdraw {amount}: balance is only {balance}"
        )

class BankAccount:
    def __init__(self, balance):
        self.balance = balance

    def withdraw(self, amount):
        if amount > self.balance:
            raise InsufficientFundsError(amount, self.balance)
        self.balance -= amount
        return self.balance

account = BankAccount(100)
try:
    account.withdraw(150)
except InsufficientFundsError as e:
    print(e)
    print(f"You tried to withdraw: {e.amount}")
    print(f"Available balance:     {e.balance}")
# Output:
# Cannot withdraw 150: balance is only 100
# You tried to withdraw: 150
# Available balance:     100

Punti chiave di questo schema:

  • super().__init__(message) imposta la stringa leggibile restituita da str(e).
  • Gli attributi aggiuntivi (self.amount, self.balance) consentono ai chiamanti di accedere a dati strutturati dall'eccezione, non solo a una stringa.
  • Una docstring chiara documenta quando l'eccezione deve essere sollevata.

Costruire una Gerarchia di Eccezioni

Le applicazioni reali hanno spesso molti tipi di errore correlati. Raggrupparli sotto una classe base condivisa consente ai chiamanti di intercettare sia l'errore specifico sia l'intera categoria:

class AppError(Exception):
    """Base class for all application errors."""

class ValidationError(AppError):
    """Raised when user input fails validation."""

class DatabaseError(AppError):
    """Raised when a database operation fails."""

def validate_username(name):
    if len(name) < 3:
        raise ValidationError(f"Username '{name}' is too short (min 3 chars)")

try:
    validate_username("ab")
except ValidationError as e:
    print(f"Validation failed: {e}")
except AppError as e:
    print(f"Application error: {e}")
# Output:
# Validation failed: Username 'ab' is too short (min 3 chars)

Un chiamante che vuole intercettare solo gli errori di database può scrivere except DatabaseError. Un chiamante che vuole intercettare qualsiasi problema dalla tua libreria può scrivere except AppError. Questo rispecchia il design della gerarchia di eccezioni di Python stessa, dove OSError raggruppa FileNotFoundError, PermissionError e molte altre.

Linee Guida per le Eccezioni Personalizzate

  • Eredita da Exception, non da BaseException. BaseException è la radice della gerarchia di Python e include anche SystemExit e KeyboardInterrupt, che non dovrebbero essere intercettati accidentalmente.
  • Termina il nome della classe in Error per le eccezioni che segnalano un problema. Questo segue le convenzioni di Python (ValueError, TypeError, IOError).
  • Mantieni la classe minimale a meno che tu non abbia bisogno di attributi aggiuntivi. Un corpo vuoto con una docstring è perfettamente valido.
  • Inserisci le eccezioni in un modulo dedicato (es. exceptions.py) per i progetti più grandi, in modo che i chiamanti possano importarle senza dover importare il resto del codice.

L'istruzione assert

assert è un modo leggero per esprimere invarianti — condizioni che devono essere vere affinché il codice sia corretto:

def divide(a, b):
    assert b != 0, "Divisor must not be zero"
    return a / b

try:
    divide(10, 0)
except AssertionError as e:
    print(f"AssertionError: {e}")

print(divide(10, 2))
# Output:
# AssertionError: Divisor must not be zero
# 5.0

assert condition, message solleva AssertionError con il messaggio specificato quando condition è False.

Limitazione importante: Python rimuove le istruzioni assert quando viene eseguito con il flag -O (ottimizza). Questo significa:

  • Usa assert solo per controlli di coerenza interna e ausili al debug.
  • Usa raise con un'eccezione appropriata per la validazione dell'input utente e i controlli API pubblici che devono essere sempre eseguiti.

Errori Comuni

Intercettare e ignorare silenziosamente le eccezioni

# Bad — the error disappears
try:
    result = risky_operation()
except Exception:
    pass

# Better — at minimum, log or re-raise
try:
    result = risky_operation()
except Exception as e:
    print(f"Operation failed: {e}")
    raise

Sollevare una stringa invece di un'eccezione

# Wrong — strings are not exceptions
raise "something went wrong"  # TypeError

# Correct
raise ValueError("something went wrong")

Intercettare BaseException accidentalmente

# Dangerous — this catches KeyboardInterrupt and SystemExit too
except BaseException:
    ...

# Use Exception instead
except Exception:
    ...
TecnicaQuando usarla
raise ExceptionType("msg")Segnalare un problema che il chiamante deve gestire
raise (senza argomenti)Risollevare l'eccezione corrente dopo la registrazione o la pulizia
raise NewError(...) from originalTradurre un errore di basso livello in uno di livello superiore, preservando la causa
raise NewError(...) from NoneTradurre un errore nascondendo la causa interna
Classe di eccezione personalizzataAssegnare agli errori specifici del dominio un tipo univoco e intercettabile
Gerarchia di eccezioniConsentire ai chiamanti di intercettare categorie di errori ampie o specifiche
assertVerificare gli invarianti interni solo durante lo sviluppo

Per il quadro completo dell'intercettazione e gestione delle eccezioni, vedi Python Try...Except. Per capire come le eccezioni personalizzate si inseriscono nel design delle classi, consulta Python Classes and Objects e Python Inheritance.

Esercitati

Pratica
Which statement correctly raises a ValueError with the message 'invalid input'?
Which statement correctly raises a ValueError with the message 'invalid input'?
Pratica
What does bare raise (with no argument) do inside an except block?
What does bare raise (with no argument) do inside an except block?
Pratica
Which base class should a custom exception inherit from?
Which base class should a custom exception inherit from?
Was this page helpful?