Python
Excepțiile în Python: Ce sunt și cum le folosim
1. Ce sunt Excepțiile?
În Python, o excepție este un obiect care reprezintă un eveniment anormal care apare în timpul execuției programului. Aceste obiecte sunt instanțe ale claselor care derivă din clasa bază BaseException.
De ce este util să le folosim?
1. Separarea responsabilităților: Codul "normal" (logică de business) nu trebuie să fie amestecat cu codul de "gestionare a erorilor".
2. Propagarea informației: Excepțiile transportă date (mesaje, tipul erorii, contextul) de la punctul în care a apărut problema până la punctul în care o gestionăm.
3. Controlul fluxului: Permite programului să continue execuția după ce o eroare a fost gestionată, în loc să se termine abrupt.
2. Structura de bază: try, except, else, finally
Sintaxa standard pentru gestionarea excepțiilor este un bloc try. Dar nu doar try și except. Python oferă două blocuri suplimentare foarte utile: else și finally.
try:
# Blocul 1: Codul care poate genera excepție
f = open("fisier_test.txt", "r")
content = f.read()
except FileNotFoundError:
# Blocul 2: Codul care se execută DOAR dacă apare FileNotFoundError
print("Fișierul nu a fost găsit!")
else:
# Blocul 3: Codul care se execută DOAR dacă NU a apărut nicio excepție
print("Fișierul a fost citit cu succes. Conținut:", content)
finally:
# Blocul 4: Codul care se execută ÎNTOTDEAUNA, indiferent dacă a fost excepție sau nu
f.close()
try::- Aici introducem codul "riscant". Python va monitoriza executarea liniilor de mai jos.
f = open("fisier_test.txt", "r"):- Încercăm să deschidem un fișier. Dacă fișierul nu există, Python va genera (arunca) o excepție
FileNotFoundError.
- Încercăm să deschidem un fișier. Dacă fișierul nu există, Python va genera (arunca) o excepție
content = f.read():- Dacă fișierul există, citim conținutul.
except FileNotFoundError::- Specificăm tipul exact de excepție pe care vrem să îl prindem. Python verifică: "A apărut
FileNotFoundError? Dacă da, execută acest bloc." - Notă importantă: Dacă ar fi apărut o altă excepție (ex:
PermissionError), acest bloc nu s-ar activa.
- Specificăm tipul exact de excepție pe care vrem să îl prindem. Python verifică: "A apărut
print("Fișierul nu a fost găsit!"):- Gestionăm eroarea specifică.
else::- Acest bloc este opțional, dar foarte util. Se execută doar dacă blocul
trys-a finalizat fără excepții. - De ce? Pentru a separa logica de succes de logica de eroare. Dacă punem codul de procesare a datelor în
else, știm sigur că datele sunt valide.
- Acest bloc este opțional, dar foarte util. Se execută doar dacă blocul
print("Fișierul a fost citit..."):- Afișăm rezultatul, deoarece funcția
opena reușit.
- Afișăm rezultatul, deoarece funcția
finally::- Acest bloc este recomandat de folosit pentru resurse (fișiere, conexiuni DB). Se execută ÎNTOTDEAUNA.
- De ce? Chiar dacă a apărut o excepție în
try, sau dacă am sărit înexcept,finallyse va executa. Este ideal pentru a curăți resursele alocate (close(),disconnect()).
f.close():- Închidem fișierul. Dacă fișierul nu a fost deschis (pentru că a fost
FileNotFoundError), această linie ar genera o eroareAttributeError. Aici trebuie să fim atenți! (Vom vedea cum să evităm asta mai jos).
- Închidem fișierul. Dacă fișierul nu a fost deschis (pentru că a fost
3. Ierarhia Excepțiilor
Toate excepțiile în Python formează un arbore ierarhic. Clasa de bază este BaseException. Sub ea se află Exception (majoritatea erorilor pe care le gestionăm noi). Sub Exception se află clase specifice.
# Arbore simplificat:
# BaseException
# |-- SystemExit
# |-- KeyboardInterrupt
# |-- GeneratorExit
# |-- Exception <-- Noi lucrăm aici
# |-- ValueError
# |-- TypeError
# |-- FileNotFoundError
# |-- ...
De ce este important să înțelegem ierarhia? Poți prinde o excepție generică și vei prinde toate subtipurile ei.
try:
int("abc")
except Exception as e:
print(f"Am prins o eroare generică: {type(e).__name__}")
int("abc"): Încercăm să convertim string-ul "abc" în int. Aceasta va generaValueError.except Exception as e:: PrindemException. DeoareceValueErroreste o subclasă aException, eroarea va fi prinsă.print(f"Am prins... {type(e).__name__}"):type(e): Returnează clasa obiectului excepție (ex:<class 'ValueError'>)..__name__: Returnează numele clasei ca string ("ValueError").- Astfel, vedem exact ce tip de eroare a fost, chiar dacă am prins-o generic.
4. Crearea unei Excepții Noi
Când erorile standard nu sunt suficient de descriptive, creăm clase proprii. O excepție custom trebuie să moștenească din Exception (sau BaseException).
class EroareLogin(Exception):
"""
Excepție personalizată pentru erori de autentificare.
"""
def __init__(self, username, mesaj="Autentificare eșuată"):
self.username = username
self.mesaj = mesaj
# Apelăm constructorul clasei părinte (Exception)
super().__init__(self.mesaj)
class EroareLogin(Exception)::- Definim o nouă clasă. Moștenirea din
Exceptioneste crucială. Dacă nu o facem, Python nu o va recunoaște ca excepție și nu o poți prinde înexcept.
- Definim o nouă clasă. Moștenirea din
def __init__(self, username, mesaj="Autentificare eșuată")::- Constructorul. Aici definim parametrii excepției.
username: Parametru specific pentru contextul login.mesaj="Autentificare eșuată": Parametru default. Dacă utilizatorul nu specifică un mesaj, se folosește acesta.
self.username = username:- Salvăm username-ul în instanța excepției. Astfel, când prindem excepția, putem accesa
e.username.
- Salvăm username-ul în instanța excepției. Astfel, când prindem excepția, putem accesa
self.mesaj = mesaj:- Salvăm mesajul.
super().__init__(self.mesaj):- Cel mai important pas!
super()se referă la clasa părinte (Exception). - Excepțiile standard afișează un mesaj când sunt printate. Noi trebuie să transmitem acest mesaj către constructorul părinte. Fără aceasta, printarea excepției ar fi goală sau ar arăta doar obiectul.
- Cel mai important pas!
5. Cum Aruncăm (Raise) Excepțiile
Acum să simulăm un sistem de login care folosește această excepție.
def autentificare(user, parola):
if parola != "secret":
# Aruncăm excepția custom
raise EroareLogin(user, f"Parola incorectă pentru {user}")
return "Login reușit"
# Testul
try:
rezultat = autentificare("alice", "1234")
except EroareLogin as e:
print(f"Eroare pentru user: {e.username}")
print(f"Detalii: {e.mesaj}")
if parola != "secret":: Verificăm condiția de eroare.raise EroareLogin(user, f"..."):raise: Cuvântul cheie care "aruncă" excepția. Întrerupe execuția curentă și propagă excepția.EroareLogin(user, f"..."): Creăm instanța. Trecemuserși un mesaj dinamic.
except EroareLogin as e::- Prindem exact tipul nostru custom.
print(f"Eroare pentru user: {e.username}"):- Accesăm atributul
usernamepe care l-am salvat în__init__.
- Accesăm atributul
print(f"Detalii: {e.mesaj}"):- Accesăm atributul
mesaj.
- Accesăm atributul
6. Gestionarea Parametrilor Default și Flexibilitate
Ce se întâmplă dacă aruncăm excepția fără a specificat mesajul? Python folosește default-ul.
def autentificare_rapida(user):
# Simulăm o eroare fără detalii extra
raise EroareLogin(user)
try:
autentificare_rapida("bob")
except EroareLogin as e:
print(f"User: {e.username}")
print(f"Mesaj Default: {e.mesaj}")
raise EroareLogin(user): Trecem doaruser.mesajrămâne valoarea default"Autentificare eșuată".print(f"Mesaj Default: {e.mesaj}"): Afișăm mesajul default, demonstrând că parametrul default funcționează.
7. Prinderea Multiplelor Excepții și Excepția Generică
Uneori, nu știi exact ce eroare poate apărea, sau vrei să gestionezi mai multe tipuri în același bloc.
try:
# Cod care poate genera ValueError sau TypeError
valoare = int("abc") + "string"
except (ValueError, TypeError) as e:
print(f"Am prins o eroare de tip {type(e).__name__}: {e}")
except Exception as e:
print(f"Am prins o altă eroare generică: {e}")
except (ValueError, TypeError) as e::- Putem specifica o tuplă de excepții. Dacă apare orice dintre ele, acest bloc se activează.
except Exception as e::- Acest bloc este un "catch-all" (prinde tot). Se execută dacă nu a fost prindă de blocurile anterioare.
- Ordinea este crucială! Blocurile specifice (
ValueError) trebuie să vină înainte de blocul generic (Exception). Python verifică în ordine. Dacă puiExceptionprimul, vei prinde toate erorile, iar blocurile specifice de mai jos nu se vor activa niciodată.
8. Propagarea Excepțiilor (Reraise)
Uneori, vrei să prindeți eroarea, să logați ceva, dar să o propagi mai departe, ca să fie gestionată la un nivel superior.
def nivel_superior():
try:
nivel_inferior()
except EroareLogin as e:
print(f"Logare: Eroare detectată la nivel inferior pentru {e.username}")
raise e # Re-aruncăm excepția, păstrând contextul
raise e:- Re-aruncăm excepția prindă. Astfel, gestionarea finală (la un nivel mai sus) va primi aceeași instanță
e, cuusernameșimesajintacte. - Fără
raise e, excepția s-ar "consuma" și nu s-ar propagi.
- Re-aruncăm excepția prindă. Astfel, gestionarea finală (la un nivel mai sus) va primi aceeași instanță
9. Best Practices: Ce NU să faci
- Nu prinde
Exceptiongeneric fără motiv: Poți ascunde bug-uri grave. Nu ignora excepția (
pass):try: f = open("file.txt") except: pass # BAD! Ignori eroarea- Dacă nu faci
pass, fișierulfnu este definit. Orice linie dupăexceptcare foloseștefva generaNameError.
- Dacă nu faci
- Folosește
finallypentru resurse: Nu închide fișierele înexceptsauelse. Foloseștefinally.
10. Exemplu Complet Executabil: Sistem de Gestionare a Resurselor
Hai să combinăm totul într-un exemplu robust.
class EroareResursa(Exception):
"""Excepție pentru probleme cu resursele."""
def __init__(self, resursa_id, tip_eroare="Eroare generica"):
self.resursa_id = resursa_id
self.tip_eroare = tip_eroare
super().__init__(f"Resursa {resursa_id}: {tip_eroare}")
def deschide_resursa(resursa_id):
if resursa_id < 0:
raise EroareResursa(resursa_id, "ID invalid")
print(f"Resursa {resursa_id} deschisă.")
return resursa_id
def procesare_resursa(resursa_id):
try:
# 1. Deschidem resursa
id_valid = deschide_resursa(resursa_id)
# 2. Procesăm (simulăm o eroare de calcul)
rezultat = 10 / id_valid
except ZeroDivisionError:
# 3. Prindem eroare specifică
print(f"Eroare matematică pentru resursa {resursa_id}")
raise EroareResursa(resursa_id, "Diviziune la zero") from ZeroDivisionError
except EroareResursa as e:
# 4. Prindem eroarea custom (propagată)
print(f"Gestionare custom: {e}")
else:
# 5. Dacă nu a fost eroare
print(f"Rezultat succes: {rezultat}")
finally:
# 6. Curățenie
print(f"Resursa {resursa_id} închisă.")
# Test 1: ID valid
print("--- Test 1 ---")
procesare_resursa(5)
# Test 2: ID invalid
print("--- Test 2 ---")
procesare_resursa(-1)
# Test 3: ID 0 (Diviziune la zero)
print("--- Test 3 ---")
procesare_resursa(0)
Explicație detaliată a fluxului:
Test 1 (ID 5):
deschide_resursa(5): Reușește.10 / 5: Reușește.else: Se execută. Afișează rezultatul.finally: Închide resursa.
Test 2 (ID -1):
deschide_resursa(-1): AruncăEroareResursa.except ZeroDivisionError: Nu se activează (nu e diviziune la zero).except EroareResursa as e: Se activează. Afișează mesajul custom.else: Nu se activează (a fost excepție).finally: Închide resursa (chiar și după excepție!).
Test 3 (ID 0):
deschide_resursa(0): Reușește (ID >= 0).10 / 0: AruncăZeroDivisionError.except ZeroDivisionError: Se activează.raise EroareResursa(...) from ZeroDivisionError:- Aruncăm o excepție custom.
from ZeroDivisionError: Chain Exception. Legăm excepția custom de cea originală. Astfel, traceback-ul va arăta căEroareResursaa fost cauzată deZeroDivisionError. Este foarte util pentru debugging.
except EroareResursa as e: Se activează (excepția custom a fost propagată).else: Nu se activează.finally: Închide resursa.
Concluzie
Excepțiile în Python sunt un mecanism puternic pentru:
1. Gestionarea erorilor (try-except).
2. Separarea logicii (else pentru succes, except pentru eroare).
3. Curățenia (finally pentru resurse).
4. Personalizarea (Clase custom cu parametri).
5. Debugging (Propagare și chaining de excepții).