2.26. Hibák kiváltása#

Egy függvény úgy jelezhet problémát a hívójának, hogy kivált egy kivételt. A kulcsszó a raise:

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

A square_root(-1) hívása a raise sornál megáll, kiugrik a függvényből, és illeszkedő except blokkot keres a hívóban. Ha egyetlen hívó sem kapja el, a szkript hibakövetéssel (traceback) ér véget.

2.26.1. Miért kiváltani, ahelyett, hogy egy jelzőértéket adnánk vissza#

Két módja a „rossz bemenet” jelzésének:

# 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

A kivételes forma általában jobb:

  • A hívónak szándékosan kell kezelnie a hibaesetet – vagy egy try blokkal, vagy úgy, hogy hagyja a kivételt továbbterjedni. A jelzőértékeket könnyű elfelejteni, és könnyű normál eredménynek nézni.

  • A hibaüzenet a kivétellel együtt utazik; a jelzőértékes megközelítésnek máshol kell csatolnia a diagnosztikai információt.

  • Az alapértelmezett viselkedés egy nem kezelt kivétel esetén egy hangos összeomlás, hibakövetéssel (traceback), amely a vétkes hívásra mutat. A néma None visszatérési értékek később finom hibákká válnak.

Csak akkor nyúlj jelzőértékekhez, amikor a „nem található” egy rutinszerű, nem kivételes kimenet – a dict.get() éppen azért ad vissza None értéket hiányzó kulcs esetén, mert a keresések várhatóan néha eredménytelenek.

2.26.2. Egyedi kivételosztályok#

Ahhoz, hogy olyan problémát válts ki, amelyet a hívó esetleg meg akar különböztetni a beépített hibáktól, definiálj egy Exception alosztályt:

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)

Az üres class törzs rendben van – maga a név az, ami számít, mert a hívók osztály szerint kapnak el. Csoportosítsd a kapcsolódó hibákat egy közös alaposztály alá, ha egy hívó esetleg az egész családot egyetlen blokkban akarja elkapni.

2.26.2.1. Újrakiváltás#

Egy üres raise egy except blokkon belül újra kiváltja az aktuális kivételt, így az a következő kezelőhöz terjed tovább:

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

Ez a helyes forma, amikor egy függvény meg akar figyelni egy hibát (naplózni, megszámolni, egy részleges változtatást visszavonni) anélkül, hogy ténylegesen kezelné.

2.26.3. Mikor kapd el, és mikor terjeszd tovább#

Egy hasznos ökölszabály:

  • Kapd el a kivételt azon a szinten, amely értelmesen helyre tud állni – helyettesíts egy alapértelmezett értéket, próbáld újra, hagyd ki a rossz bemenetet.

  • Hagyd továbbterjedni, ha nincs semmi hasznos teendő az összeomláson kívül, vagy amikor a felette lévő réteg az, amely tudja, hogyan álljon helyre.

Egy hívási verem közepén lévő függvény, amely minden hibát elnyel, és némán visszatér, követhetetlenné teszi a hibákat. Inkább hagyd a kivételeket utazni, amíg el nem érnek olyan kódot, amelynek valóban van terve.