În acest tutorial veți învăța cum să gestionați condițiile de eroare din Python din punct de vedere al întregului sistem. Gestionarea erorilor este un aspect critic al designului și trece de la cele mai joase niveluri (uneori hardware-ul) până la utilizatorii finali. Dacă nu aveți o strategie coerentă, sistemul dvs. va fi nesigur, experiența utilizatorului va fi slabă și veți avea multe provocări de depanare și depanare.
Cheia succesului este conștientizarea tuturor acestor aspecte interconectate, considerându-le în mod explicit și formând o soluție care abordează fiecare punct.
Există două modele principale de tratare a erorilor: codurile de stare și excepțiile. Codurile de stare pot fi utilizate de orice limbaj de programare. Excepțiile necesită suport pentru limbă / runtime.
Python acceptă excepții. Python și biblioteca standard folosesc excepții în mod liber pentru a raporta despre multe situații excepționale, cum ar fi erorile IO, împărțirea la zero, indexarea limitelor și, de asemenea, unele situații care nu sunt atât de excepționale, cum ar fi sfârșitul iterației (deși este ascuns). Majoritatea bibliotecilor urmează exemplul și ridică excepții.
Aceasta înseamnă că codul dvs. va trebui oricum să se ocupe de excepțiile ridicate de Python și de biblioteci, astfel încât să puteți ridica excepții de la cod atunci când este necesar și să nu vă bazați pe codurile de stare.
Înainte de a scufunda în sanctumul intern al excepțiilor Python și de a rezolva cele mai bune practici cu privire la erori, să vedem cum se face o manipulare a excepțiilor în acțiune:
def (): return 4/0 def g (): ridica Excepție ("Nu ne sunați, vă vom apela") def h (): try: încercați: g () cu excepția Excepție ca e: print (e)
Aici este ieșirea la apel h ()
:
h () împărțirea cu zero Nu ne sunați. Te sunăm
Excepțiile Python sunt obiecte organizate într-o ierarhie de clase.
Aici este întreaga ierarhie:
BaseException + - SystemExit + - KeyboardInterrupt + - GeneratorExit + - Excepție + - StopIteration + - StandardError | + - BufferError | + - AritmeticError | | + - FloatingPointError | | + - OverflowError | + - ZeroDivisionError | + - AsertionError | + - AtributError | + - MediuError | | + - IOError | | + - OSError | | + - WindowsError (Windows) | + - VMSError (VMS) + - EOFError | + - ImportError | + - LookupError | + - IndexError | | + - KeyError | + - MemoryError | + - NumeError | | + - NeboundLocalError | + - ReferenceError | + - RuntimeError | | + - NotImplementedError | + - SyntaxError | | + - IndentationError | | + - TabError | + - SystemError | + - TypeError | + - ValueError | + - UnicodeError | + - UnicodeDecodeError | + - UnicodeEncodeError | + - UnicodeTranslateError + - Avertisment + - DeprecationWarning + - PendingDeprecationWarning + - RuntimeWarning + - SintaxăWarning + - UserWarning + - FutureWarning + - ImportWarning + - UnicodeWarning + - BytesWarning
Există câteva excepții speciale care derivă direct din BaseException
, ca SystemExit
, KeyboardInterrupt
și GeneratorExit
. Apoi există Excepție
clasa, care este clasa de bază pentru StopIteration
, Eroare standard
și Avertizare
. Toate erorile standard sunt derivate din Eroare standard
.
Când ridicați o excepție sau o anumită funcție pe care ați apelat-o ridică o excepție, fluxul normal de cod se încheie și excepția începe să răspândească stiva de apel până când întâlnește un handler de excepție corespunzător. Dacă nu este disponibil un manipulator de excepții pentru a se ocupa de el, procesul (sau, mai precis, firul curent) va fi încheiat cu un mesaj de excepție nefolosit.
Creșterea excepțiilor este foarte ușoară. Folosiți doar a ridica
cuvânt cheie pentru a ridica un obiect care este o sub-clasă a Excepție
clasă. Ar putea fi un exemplu Excepție
ea însăși, una dintre excepțiile standard (de ex. Eroare de rulare
) sau o subclasă de Excepție
te-ai derivat. Iată un mic fragment care demonstrează toate cazurile:
# Creșteți o instanță a clasei Excepție însăși ridicați Excepție ("Ummm ... ceva este greșit") # Ridicați o instanță a clasei RuntimeError ridicați RuntimeError ('Ummm ... ceva este greșit') # Ridicați o subclasă particulară de Excepție care păstrează timestamp excepția a fost creată de la datetime import datetime class SuperError (Excepție): def __init __ (auto, mesaj): Excepție .__ init __ (mesaj) self.when = datetime.now () ridica SuperError ('Ummm ... ceva este greșit')
Ați prins excepții cu cu exceptia
clauza, după cum ați văzut în exemplu. Când capturați o excepție, aveți trei opțiuni:
Ar trebui să înghițiți excepția dacă știți cum să o faceți și puteți să vă recuperați complet.
De exemplu, dacă primiți un fișier de intrare care poate fi în diferite formate (JSON, YAML), puteți încerca să îl parsați folosind diferite parseruri. Dacă parserul JSON a ridicat o excepție că fișierul nu este un fișier JSON valid, îl înghiți și încercați cu parserul YAML. Dacă parserul YAML a eșuat prea mult, atunci lăsați excepția să se răspândească.
importați json import yaml def parse_file (nume fișier): try: return json.load (deschis (nume fișier)) cu excepția json.JSONDecodeError return yaml.load (open (filename))
Rețineți că alte excepții (de ex., Fișierul nu a fost găsit sau permisiunile de citire) nu se vor propaga și nu vor fi incluse în clauza excepțională. Aceasta este o politică bună în acest caz în care doriți să încercați analiza YAML numai dacă parsarea JSON a eșuat din cauza unei probleme de codare JSON.
Dacă vrei să te descurci toate excepții, atunci utilizați doar cu excepția excepției
. De exemplu:
def print_exception_type (func, * args, ** kwargs): încercați: return func (* args, ** kwargs) cu excepția Excepție ca e: tip de tipărire (e)
Rețineți că prin adăugarea ca e
, legați obiectul excepție de numele e
disponibile în clauza dvs. excepțională.
Pentru a re-raise, trebuie doar să adăugați a ridica
fără argumente în interiorul robotului tău. Acest lucru vă permite să efectuați anumite manipulări locale, dar totodată permiteți nivelurilor superioare să se ocupe de asemenea. Aici invoke_function ()
funcția imprimă tipul de excepție de la consolă și apoi re-ridică excepția.
Definiți funcția (func, * args, ** kwargs): try: return func (* args, ** kwargs) cu excepția Excepție ca e: tip de tipărire (e)
Există mai multe cazuri în care doriți să ridicați o excepție diferită. Uneori doriți să grupați mai multe excepții de nivel scăzut într-o singură categorie care este tratată uniform de codul de nivel superior. În cazurile de ordonare, trebuie să transformați excepția la nivelul utilizatorului și să furnizați anumite contexte specifice aplicației.
Uneori doriți să asigurați că un anumit cod de curățare se execută chiar dacă o excepție a fost ridicată undeva de-a lungul drumului. De exemplu, este posibil să aveți o conexiune la baza de date pe care doriți să o închideți după ce ați terminat. Iată un mod greșit de a face acest lucru:
Definiți fetch_some_data (): db = open_db_connection () interogare (db) close_db_Connection (db)
În cazul în care query ()
funcția ridică o excepție, apoi apelul la close_db_connection ()
nu va executa niciodată și conexiunea DB va rămâne deschisă. in cele din urma
clauza se execută întotdeauna după o încercare, se execută totul. Iată cum să procedați corect:
(db) în cele din urmă: dacă db nu este Nici unul: close_db_connection (db)
Apelul la open_db_connection ()
poate să nu returneze o conexiune sau să ridice singură excepția. În acest caz, nu este necesar să închideți conexiunea DB.
Atunci când se utilizează in cele din urma
, trebuie să fiți atenți să nu ridicați excepții acolo, deoarece acestea vor masca excepția inițială.
Managerii de context furnizează un alt mecanism pentru a împacheta resurse precum fișiere sau conexiuni DB în codul de curățare care se execută automat chiar și atunci când au fost ridicate excepții. În loc să încercați blocurile finale, utilizați cu
afirmație. Iată un exemplu cu un fișier:
def process_file (nume de fișier): cu deschis (nume fișier) ca f: process (f.read ())
Acum, chiar dacă proces()
a ridicat o excepție, dosarul va fi închis în mod corespunzător imediat, atunci când domeniul de aplicare al cu
bloc este oprit, indiferent dacă excepția a fost gestionată sau nu.
Logarea este destul de mult o cerință în sistemele non-triviale, de lungă durată. Este utilă în special în aplicațiile web unde puteți trata toate excepțiile într-un mod generic: trebuie doar să înregistrați excepția și să returnați un mesaj de eroare apelantului.
La logare, este util să înregistrați tipul de excepție, mesajul de eroare și stacktrace. Toate aceste informații sunt disponibile prin sys.exc_info
obiect, dar dacă utilizați logger.exception ()
în sistemul de tratare a excepțiilor, sistemul de înregistrare Python va extrage toate informațiile relevante pentru dvs..
Aceasta este cea mai bună practică pe care o recomand:
logging logging logging = logging.getLogger () def f (): try: flaky_func () cu excepția excepției: logger.exception () raise
Dacă urmați acest model, atunci (presupunând că ați configurat logarea corect), indiferent de ce se întâmplă, veți avea o înregistrare destul de bună în jurnalele dvs. despre ceea ce nu a mers bine și veți putea să remediați problema.
Dacă reînviți, asigurați-vă că nu vă înregistrați aceeași excepție de mai multe ori la diferite nivele. Este o risipă și s-ar putea să vă deruteze și să vă facă să credeți că au apărut mai multe instanțe ale aceleiași probleme, când, în practică, o singură instanță a fost înregistrată de mai multe ori.
Cea mai simplă metodă este de a permite propagarea tuturor excepțiilor (cu excepția cazului în care acestea pot fi tratate cu încredere și înghițite mai devreme) și apoi faceți logarea aproape de nivelul de sus al aplicației / sistemului dvs..
Logarea este o capabilitate. Cea mai comună implementare este utilizarea fișierelor de jurnal. Dar, pentru sistemele distribuite pe scară largă cu sute, mii sau mai multe servere, aceasta nu este întotdeauna cea mai bună soluție.
Pentru a urmări excepțiile de-a lungul întregii dvs. infrastructuri, un serviciu, cum ar fi santinele, este foarte util. Centrează toate rapoartele excepționale și, pe lângă stacktrace, adaugă starea fiecărui cadru de stivă (valoarea variabilelor în momentul creșterii excepției). De asemenea, oferă o interfață foarte plăcută cu tablouri de bord, rapoarte și modalități de a defila mesajele prin mai multe proiecte. Este open source, astfel încât să puteți rula propriul server sau să vă abonați la versiunea găzduită.
Unele defecțiuni sunt temporare, în special atunci când se ocupă de sisteme distribuite. Un sistem care izbucnește la primul semn de probleme nu este foarte util.
Dacă codul dvs. accesează un sistem la distanță care nu răspunde, soluția tradițională este timpul de expirare, dar uneori nu fiecare sistem este proiectat cu timeouts. Timeouts nu sunt întotdeauna ușor de calibrat deoarece condițiile se schimbă.
O altă abordare este să nu reușiți repede și apoi să încercați din nou. Beneficiul este ca, daca tinta raspunde rapid, atunci nu trebuie sa petreceti mult timp in starea de somn si sa reactionati imediat. Dar dacă nu a reușit, puteți reîncerca de mai multe ori până când decideți că este într-adevăr imposibil de realizat și ridicați o excepție. În următoarea secțiune, vă voi prezenta un decorator care vă poate face acest lucru.
Doi decoratori care pot ajuta la manipularea erorilor sunt @log_error
, care înregistrează o excepție și apoi o re-ridică, și @retry
decorator, care va reîncerca numirea unei funcții de mai multe ori.
Iată o implementare simplă. Decoratorul exclude un obiect logaritm. Când decorează o funcție și funcția este invocată, ea va înfășura apelul într-o clauză de încercare, cu excepția cazului în care există o excepție, o va loga și, în final, va re-ridica excepția.
def log_error (logger) def decorat (f): @ functools.wraps (f) def wrap (* args, ** kwargs): try: return f (* args, ** kwargs) .excepție (e) să ridice întoarcerea înfășurată returnate decorate
Iată cum se utilizează:
logging logging logging = logging.getLogger () @ log_error (logger) def f (): ridicați Excepție ("Sunt excepțional")
Iată o implementare foarte bună a decoratorului @retry.
importați timp de math de import # Refaceți un decorator cu exponențial de întoarcere (încearcă, întârziere = 3, backoff = 2): "Întoarce o funcție sau o metodă până când revine la întârzierea întârzierii întârzierii inițiale în secunde și backoff stabilește factorul prin care întârzierea ar trebui să se prelungească după fiecare eșec.backoff trebuie să fie mai mare de 1, altfel nu este într-adevăr un backoff.încercări trebuie să fie cel puțin 0, și întârziere mai mare de 0. "dacă backoff <= 1: raise ValueError("backoff must be greater than 1") tries = math.floor(tries) if tries < 0: raise ValueError("tries must be 0 or greater") if delay <= 0: raise ValueError("delay must be greater than 0") def deco_retry(f): def f_retry(*args, **kwargs): mtries, mdelay = tries, delay # make mutable rv = f(*args, **kwargs) # first attempt while mtries > 0: dacă rv este Adevărat: # Efectuat la întoarcerea la succes True mtries - = 1 # consumă o încercare time.sleep (mdelay) # așteptați ... mdelay * = backoff # așteptați mai târziu rv = f (* args, ** kwargs) # Încercați din nou să reveniți Falsă # Ați ieșit din încercări :-( Întoarceți f_retry # Decorator adevărat -> Decorează funcția return deco_retry # @retry (arg [, ...]) -> adevărat decorator
Gestionarea erorilor este crucială atât pentru utilizatori, cât și pentru dezvoltatori. Python oferă un mare suport în limba și în biblioteca standard pentru tratarea erorilor pe baza excepțiilor. Urmând cele mai bune practici cu sârguință, puteți cuceri acest aspect adesea neglijat.
Aflați Python cu ghidul nostru complet de instrucțiuni Python, indiferent dacă sunteți doar începători sau sunteți un coder experimentat în căutarea unor noi abilități.