post

Python ro:Excepţii

Contents

Introducere

Excepţiile pot apărea atunci când apar anumite situaţii excepţionale în program. De exemplu, dacă trebuie să citiţi un fişier, dar acesta nu există? Sau dacă l-aţi şters accidental în timpul rulării programului? Astfel de situaţii sunt tratate folosind excepţii.

Similar, daca în program erau câteva declaraţii invalide? Aceste situaţii sunt tratate de Python care ridică mâinile şi vă spune ca există o eroare.

Erori

Iată un exemplu simplu de apel al funcţiei print. daca aţi scris greşit Print în loc de print? Observaţi majuscula. În acest caz, Python ridică o eroare de sintaxă.

   >>> Print('Hello World')
   Traceback (most recent call last):
     File "<pyshell#0>", line 1, in <module>
       Print('Hello World')
   NameError: name 'Print' is not defined
   >>> print('Hello World')
   Hello World

Observaţi că a fost ridicată o eroare NameError şi a fost indicată poziţia unde a fost detectată eroarea. Asta este acţiunea unei rutine pentru tratarea erorii (engl. error handler).

Excepţii

Vom încerca (engl. try) să preluăm date de la utilizator. Daţi ctrl-d şi vedeţi ce se întâmplă.

   >>> s = input('Introduceţi ceva --> ')
   Introduceţi ceva -->
   Traceback (most recent call last):
     File "<pyshell#2>", line 1, in <module>
       s = input('Introduceţi ceva --> ')
   EOFError: EOF when reading a line

Python ridică o eroare numită EOFError ceea ce înseamnă în esenţă că a găsit simbolul end of file (care se reprezinta prin ctrl-d) întrun moment în care nu era posibil.

Tratarea excepţiilor

Putem trata excepţiile folosind declaraţia try..except. Pe scurt, punem declaraţiile uzuale în blocul try şi toate tratamentele de erori în blocul except.

#!/usr/bin/python
# Fişier: try_except.py

try:
    text = input('Introduceţi ceva --> ')
except EOFError:
    print('De ce îmi dai EOF?')
except KeyboardInterrupt:
    print('Aţi anulat operaţia.')
else:
    print('Aţi introdus {0}'.format(text))

Rezultat:

   $ python try_except.py
   Introduceţi ceva -->     # Daţi ctrl-d
   De ce îmi dai EOF?

   $ python try_except.py
   Introduceţi ceva -->     # Daţi ctrl-c
   Aţi anulat operaţia.

   $ python try_except.py
   Introduceţi ceva --> # fără excepţii, acum
   Aţi introdus fără excepţii, acum

Cum funcţionează:

Punem toate declaraţiile care ar putea ridica excepţii/erori în blocul try şi apoi punem rutine de tratare pentru erorile/excepţiile respective în blocul except. Clauza except poate trata o singură eroare sau excepţie specificată, sau o listă în paranteze a excepţiilor/erorilor. Dacă nu sunt date nume de erori/excepţii, sunt tratate toate erorile şi excepţiile.

Reţineţi că trebuie să existe măcar o clauză except asociată cu fiecare clauză try. În caz contrar ce înteles are un bloc try?

Dacă o eroare sau excepţie nu este tratată, este apelată rutina implicită de tratare a limbajului Python care pur şi simplu opreşte execuţia programului şi tipăreste pe ecran un mesaj de eroare. Am văzut deja asta.

Mai poate exista şi o clauză else asociată cu un bloc try..except. Clauza else este executată dacă nu apare nici o excepţie/eroare.

În următorul exemplu vom vedea cum să obţinem obiectul excepţie, pentru a-i extrage informaţii suplimentare.

Ridicarea excepţiilor

Puteti ridica excepţii folosind declaraţia raise şi furnizînd numele erorii/excepţiei şi obiectul care urmează a fi aruncat (engl. thrown).

Eroarea sau excepţia pe care o ridicaţi trebuie să fie o clasă derivată direct sau indirect din clasa Exception.

#!/usr/bin/python
# Fişier: raising.py

class ShortInputException(Excepţie):
    '''O clasa de excepţii definită de utilizator.'''
    def __init__(self, lungime, minim):
        Excepţie.__init__(self)
        self.lungime = lungime
        self.minim = minim

try:
    text = input('Introduceţi ceva --> ')
    if len(text) < 3:
        raise ShortInputException(len(text), 3)
    # Alte prelucrări pot continua aici ca înainte
except EOFError:
    print('De ce îmi dai EOF?')
except ShortInputException as ex:
    print('ShortInputException: Aţi introdus un text de lungime {0}, era necesar minim {1}'
          .format(ex.lungime, ex.minim))
else:
    print('Nu a fost ridicată nici o excepţie.')

Rezultat:

   $ python raising.py
   Introduceţi ceva --> a
   ShortInputException: Aţi introdus un text de lungime 1, era necesar minim 3

   $ python raising.py
   Introduceţi ceva --> abc
   Nu a fost ridicată nici o excepţie.

Cum funcţionează:

Aici creăm un tip propriu de excepţie. Acest tip nou de excepţie este numit ShortInputException. Are două câmpuri – lungime care este lungimea introducerii şi minim care este lungimea minimă acceptată de program.

În clauza except menţionăm clasa de erori care va fi stocată sub numele (engl. as) variabilei care va păstra obiectul excepţie. Asta este analog parametrilor şi argumentelor în apelul funcţiei. Anume în această clauză except folosim câmpurile lungime şi minim ale obiectului excepţie pentru a tipări un mesaj adecvat către utilizator.

try .. finally

Să presupunem că vreţi să citiţi un fişier întrun program. Cum vă asiguraţi că fişierul a fost închis corect indiferent dacă a apărut sau nu o eroare? Asta se poate face folosind blocul finally. Observaţi că puteţi folosi o clauză except împreună cu clauza finally pentru acelaşi bloc try. Va trebui să le imbricaţi dacă le vreţi pe amândouă.

#!/usr/bin/python
# Fişier: finally.py

import time

try:
    f = open('poem.txt')
    while True: # citirea obişnuită
        line = f.readline()
        if len(line) == 0:
            break
        print(line, end='')
        time.sleep(2) # Ca să ne asigurăm că funcţionează un timp
except KeyboardInterrupt:
    print('!! Aţi anulat citirea din fişier.')
finally:
    f.close()
    print('(Curăţenie: Am închis fişierul)')

Rezultat:

   $ python finally.py
   Programming is fun
   When the work is done
   if you wanna make your work also fun:
  !! Aţi anulat citirea din fişier.
   (Curăţenie: Am închis fişierul)

Cum funcţionează:

Facem treburile curente la citirea fişierelor, dar avem o aşteptare de două secunde introdusă arbitrar după tipărirea fiecarei linii folosind funcţia time.sleep ca să ruleze mai lent (Python este foarte rapid prin natura lui). Când programul încă rulează apasaţi ctrl-c pentru a întrerupe programul.

Observaţi că este aruncată excepţia KeyboardInterrupt şi programul se încheie. Totuşi, înainte ca programul să se încheie, clauza finally se execută şi obiectul fişier este întotdeauna închis.

Declaraţia with

Achiziţia unei resurse în blocul try şi eliberarea ei în blocul finally este o schemă obişnuită. Din acest motiv există şi o declaraţie with care permite ca această utilizare a resursei să se facă într-o manieră mai clară:

#!/usr/bin/python
# Fişier: using_with.py

with open("poem.txt") as f:
    for line in f:
        print(line, end='')

Cum funcţionează:

Rezultatul trebuie să fie la fel ca în exemplul precedent, diferenţa este că acum folosim funcţia open cu declaraţia with – lăsăm închiderea fişierului să fie făcută automat de declaraţia with open ....

Ce se întâmplă în culise este un protocol utilizat de declaraţia with. Ea extrage obiectul întors de funcţia open, să-i zicem “fişierul” în acest caz.

Ea apelează întotdeauna funcţia fişierul.__enter__ înainte de a începe blocul de declaraţii pe care îl include şi funcţia fişierul.__exit__ după încheierea blocului de declaraţii.

Astfel codul care ar fi trebuit să fie scris în blocul finally este înlocuit automat de metoda __exit__. Astfel suntem ajutaţi să evităm utilizarea explicită a declaraţiilor try..finally în mod repetat.

Discuţia mai aprofundată a acestui subiect este dincolo de obiectivul acestei cărti, deci vă rog să citiţi PEP 343 pentru o explicaţie completă.

Rezumat

Am discutat utilizarea declaraţiilor try..except şi try..finally. Am văzut cum putem crea propriul tip de excepţie şi cum să ridicăm excepţii.

Vom explora în continuare biblioteca Python standard.