2.26. Vyvolávání chyb

Funkce může svému volajícímu signalizovat problém vyvoláním výjimky. Klíčovým slovem je raise:

def square_root(x):
    if x < 0:
        raise ValueError("square_root expects a non-negative number")
    return x ** 0.5

Volání square_root(-1) se zastaví na řádku raise, vyskočí z funkce a hledá odpovídající except ve volajícím. Pokud ji žádný volající nezachytí, skript skončí s výpisem zásobníku.

2.26.1. Proč vyvolat výjimku místo vrácení sentinelu

Dva způsoby, jak ohlásit „špatný vstup“:

# signal with a sentinel
def square_root_or_none(x):
    if x < 0:
        return None
    return x ** 0.5

# raise an exception
def square_root(x):
    if x < 0:
        raise ValueError("...")
    return x ** 0.5

Forma s výjimkou je obvykle lepší:

  • Volající musí chybový případ záměrně ošetřit – buď pomocí try, nebo tím, že nechá výjimku se šířit. Sentinely je snadné zapomenout a snadné je zaměnit za normální výsledek.

  • Chybová zpráva cestuje s výjimkou; přístup se sentinelem musí diagnostiku připojit někam jinam.

  • Výchozím chováním u nezpracované výjimky je hlasitý pád s výpisem zásobníku, který ukazuje na problematické volání. Tiché návraty None se později stanou skrytými chybami.

Po sentinelech sahejte jen tehdy, když je „nenalezeno“ běžným, neviýjimečným výsledkem – dict.get() vrací None u chybějícího klíče právě proto, že se očekává, že vyhledávání někdy minou.

2.26.2. Vlastní třídy výjimek

Chcete-li vyvolat problém, který by mohl chtít volající odlišit od vestavěných chyb, definujte podtřídu Exception:

class ConfigError(Exception):
    pass

def load_config(path):
    try:
        f = open(path)
    except OSError as e:
        raise ConfigError("missing config file: " + path)

try:
    load_config("settings.json")
except ConfigError as e:
    print("startup failed:", e)

Prázdné tělo class je v pořádku – důležitý je samotný název, protože volající zachytávají podle třídy. Seskupte související chyby pod společný základ, pokud by volající mohl chtít zachytit celou rodinu v jednom bloku.

2.26.2.1. Opětovné vyvolání

Holé raise uvnitř bloku except znovu vyvolá aktuální výjimku, takže se šíří k další obslužné rutině:

try:
    do_work()
except Exception as e:
    log(e)
    raise        # let it keep going

Toto je správný tvar, když chce funkce chybu pozorovat (zaznamenat ji, započítat, vrátit zpět částečnou změnu), aniž by ji skutečně zpracovala.

2.26.3. Kdy zachytit a kdy nechat šířit

Užitečné pravidlo:

  • Zachyťte výjimku na úrovni, která se z ní může smysluplně zotavit – nahradit výchozí hodnotou, zopakovat, přeskočit špatný vstup.

  • Nechte ji šířit, když není nic užitečného k udělání kromě pádu, nebo když vrstva nad ní je ta, která ví, jak se zotavit.

Funkce uprostřed zásobníku volání, která spolkne každou chybu a tiše se vrátí, činí selhání nevysledovatelnými. Dejte přednost tomu nechat výjimky cestovat, dokud nedosáhnou kódu, který má skutečně nějaký plán.