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
tryblokkal, 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
Nonevisszaté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.