2.26. Podizanje pogrešaka#

Funkcija može signalizirati problem svom pozivatelju podizanjem iznimke. Ključna riječ je raise:

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

Poziv square_root(-1) zaustavlja se na retku raise, izlazi iz funkcije i traži odgovarajući except u pozivatelju. Ako je nijedan pozivatelj ne uhvati, skripta završava s tragom poziva (traceback).

2.26.1. Zašto podizati umjesto vraćanja stražarske vrijednosti#

Dva načina za prijavu „loš ulaz”:

# 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

Oblik s iznimkom obično je bolji:

  • Pozivatelj mora namjerno obraditi slučaj pogreške – bilo pomoću try, bilo dopuštanjem da se iznimka proširi. Stražarske vrijednosti lako se zaborave i lako se zamijene s normalnim rezultatom.

  • Poruka o pogrešci putuje uz iznimku; pristup sa stražarskom vrijednošću mora pričvrstiti dijagnostiku negdje drugdje.

  • Zadano ponašanje pri neobrađenoj iznimci je glasno rušenje s tragom poziva koji upućuje na sporni poziv. Tiho vraćanje None postaje suptilna pogreška kasnije.

Posegnite za stražarskim vrijednostima samo kada je „nije pronađeno” rutinski, neiznimni ishod – dict.get() vraća None na nedostajući ključ upravo zato što se očekuje da pretraživanja ponekad promaše.

2.26.2. Prilagođene klase iznimki#

Da biste podigli problem koji bi pozivatelj možda želio razlikovati od ugrađenih pogrešaka, definirajte podklasu 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)

Prazno tijelo class je u redu – samo ime je ono što je bitno, jer pozivatelji hvataju prema klasi. Grupirajte povezane pogreške pod zajedničkom osnovom ako bi pozivatelj možda želio uhvatiti cijelu obitelj u jednom bloku.

2.26.2.1. Ponovno podizanje#

Goli raise unutar except bloka ponovno podiže trenutnu iznimku tako da se širi do sljedećeg rukovatelja:

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

Ovo je pravi oblik kada funkcija želi promatrati pogrešku (zabilježiti je, prebrojati je, poništiti djelomičnu promjenu) bez stvarne obrade.

2.26.3. Kada hvatati, a kada propustiti dalje#

Korisno pravilo:

  • Uhvatite iznimku na razini koja se može smisleno oporaviti – zamijenite zadanom vrijednošću, ponovite pokušaj, preskočite loš ulaz.

  • Pustite je da se proširi kada nema ničeg korisnog za učiniti osim rušenja, ili kada je sloj iznad onaj koji zna kako se oporaviti.

Funkcija u sredini stoga poziva koja proguta svaku pogrešku i tiho se vrati čini neuspjehe nemogućima za praćenje. Preferirajte puštanje iznimki da putuju dok ne dođu do koda koji zaista ima plan.