13. Εξαιρέσεις

Όλοι οι προγραμματιστές κατά την ανάπτυξη κώδικα, όσο προσεκτικοί κι αν είναι, έχουν να κάνουν με την εμφάνιση λαθών. Τα λάθη μπορεί να είναι απλά, όπως προβλήματα στοίχισης η και κρίσιμα που να οδηγούν στην κατάρρευση του συστήματος. Κατά την ανάπτυξη ενός προγράμματος είναι αδύνατο στο να μην συμβούν λάθη και ειδικά στις μεγάλες εφαρμογές, όπου η πιθανότητα εμφάνισης λαθών είναι μεγάλη. Υπάρχουν τρεις τύποι λαθών, τα συντακτικά λάθη, τα λογικά λάθη και λάθη κατά την εκτέλεση του κώδικα.

Τα συντακτικά λάθη τα κάνει ένας προγραμματιστής κατά τη φάση υλοποίησης του προγράμματος. Συντακτικά λάθη είναι για παράδειγμα η λανθασμένη συγγραφή  μιας δεσμευμένης λέξης της Python ή η απουσία εισαγωγικών μιας συμβολοσειράς. Τέτοια λάθη που οφείλονται πάνω στην συγγραφή του κώδικα ανιχνεύονται από τον διερμηνευτή , ο οποίος εμφανίζει ένα προειδοποιητικό μήνυμα. Τα σύγχρονα προγράμματα ανάπτυξης κώδικα Python, μας βοηθούν στην ανίχνευση συντακτικών λαθών, για παράδειγμα με το χρωματισμό τμημάτων κώδικα ή με την εμφάνιση ενός πλαισίου με προειδοποιητικό μήνυμα.

Τα λογικά λάθη συμβαίνουν κατά τη φάση του σχεδιασμού ενός προγράμματος, για παράδειγμα η λάθος εφαρμογή ενός τύπου υπολογισμού μιας ποσότητας. Σε αυτή την περίπτωση ο διερμηνευτής της Python δεν ανιχνεύει κάποιο λάθος κατά τη φάση εκτέλεσης, αλλά παίρνουμε μη αναμενόμενα αποτελέσματα. Τα λογικά λάθη δεν μπορούν να ανιχνευτούν από την εφαρμογή της Python, παρά μόνο με έλεγχο και ανάλυση των αποτελεσμάτων που δίνει η εκτέλεση του κώδικα.

Λάθη κατά την εκτέλεση. Τα λάθη αυτά που προκαλούνται κατά το χρόνο εκτέλεσης του προγράμματος προκαλούν τον αντικανονικό τερματισμό της εφαρμογής και τελικά το κρέμασμα του συστήματος. Τα λάθη αυτά οφείλονται σε καταστάσεις που δεν μπορούν να ελεγχθούν από τον προγραμματιστή, με αποτέλεσμα η πρόληψη τους να είναι εξαιρετικά δύσκολη. Τέτοια λάθη είναι η διαίρεση με το μηδέν, η ασυμβατότητα τύπων, δείκτης έξω από τα όρια ή την δυσλειτουργία του υλικού μέρους του υπολογιστή όπως η καταστροφή του σκληρού δίσκου, προβλήματα με την σύνδεση με το δίκτυο ή αποσύνδεση του εκτυπωτή.

Τα λάθη που συμβαίνουν κατά τη διάρκεια εκτέλεσης ενός προγράμματος Python ονομάζονται «εξαιρέσεις». Μια εξαίρεση στην Python είναι ένα αντικείμενο που αντιπροσωπεύει ένα σφάλμα.

Οι εξαιρέσεις μας βοηθούν να αντιμετωπίσουμε απρόβλεπτα λάθη που συμβαίνουν κατά τη διάρκεια εκτέλεσης ενός προγράμματος. Όταν συμβεί ένα τέτοιο σφάλμα η Python εγείρει μια εξαίρεση. Μια εξαίρεση είναι ένα αντικείμενο σφάλματος το οποίο μπορούμε να το διαχειριστούμε με κατάλληλες εντολές. Αν δεν διαχειριστούμε μια εξαίρεση κατάλληλα, η Python θα μας δώσει ένα αναλυτικό μήνυμα λάθους σε μορφή μηνύματος Traceback. Ένα μήνυμα Traceback δίνει αναλυτικές πληροφορίες για να εντοπίσουμε το σημείο του προγράμματος που δίνει το σφάλμα κατά την εκτέλεση του κώδικα.

Η Python διαχειρίζεται εξαιρέσεις (κατ’ εξαίρεση σφάλματα) με την ακόλουθη σύνταξη:

try:
     εντολές που πιθανό να προκαλέσουν εξαίρεση
except τύπος_εξαίρεσης_1:
    εντολές_χειρισμού_εξαίρεσης_1
. . .
except τύπος_εξαίρεσης_Ν:
    εντολές_χειρισμού_εξαίρεσης_Ν

Στο τμήμα try: γράφουμε τον κώδικα που πιθανό να δώσει μια εξαίρεση. Στη συνέχεια ακολουθεί ένα ή περισσότερα τμήματα except από όπου βάζουμε τον αντίστοιχο κώδικα χειρισμού της εξαίρεσης. Μπορούμε σε ένα τμήμα except να μην αναφερθούμε σε ένα τύπο εξαίρεσης, αλλά αυτό έχει σαν αποτέλεσμα να συλληφθούν όλες οι εξαιρέσεις. Αυτό μπορεί να προκαλέσει πρόβλημα, για παράδειγμα να συλληφθεί μια εξαίρεση του συστήματος που να είναι άσχετη με τον κώδικα μας.

Μια εξαίρεση που συμβαίνει συχνά είναι η διαίρεση με το μηδέν. Αν γίνει μια διαίρεση με το μηδέν προκύπτει το ακόλουθο μήνυμα Traceback:

>>> 10/0
        Traceback (most recent call last):
        File "<pyshell#0>", line 1, in <module>
        10/0

Μπορούμε να χρησιμοποιήσουμε την εντολή try – except για να διαχειριστούμε την εξαίρεση ZeroDivisionError έτσι ώστε να μην καταρρεύσει το σύστημα:

try:
      20/0
except  ZeroDivisionError as error:
      print('Διαίρεση με το μηδέν')
      print(error)

Αν τρέξουμε το προηγούμενο απόσπασμα κώδικα θα πάρουμε την ακόλουθη έξοδο:

Διαίρεση με το μηδέν
division by zero

Μια άλλη εξαίρεση που συμβαίνει συχνά είναι η εισαγωγή μιας τιμής με λάθος τύπο. Για παράδειγμα το ακόλουθο απόσπασμα κώδικα χρησιμοποιεί την εντολή try – except για να μην καταρρεύσει το σύστημα όταν δοθεί λάθος τύπος τιμής.

>>> try:
         x=int(input('Δώσε τον αριθμό:  '))
    except ValueError as e:
         print(e)
         print('Δώσε ακέραιο αριθμό ')
        
    Δώσε τον αριθμό:  θ89
    invalid literal for int() with base 10: 'θ89'
    Δώσε ακέραιο αριθμό
>>>

Μια άλλη περίπτωση εξαίρεσης είναι όταν εισάγουμε λάθος τιμή σε ένα δείκτη μιας λίστας από όπου παίρνουμε την εξαίρεση τύπου IndexError. Με τη σύλληψη αυτής της εξαίρεσης, προστατεύουμε να μην καταρρεύσει το σύστημα στην περίπτωση που δοθεί λάθος τιμή σε δείκτη μιας λίστας. Ακολουθεί ένα παράδειγμα:

>>> mylist = ['k', 'l', 'm']
>>> try:
        print(mylist[6])
    except  IndexError as error:
        print(error)
        print('Δείκτης εκτός ορίων')
 
    list index out of range
    Δείκτης εκτός ορίων
>>>

Στην εντολή try – except μπορούμε να χρησιμοποιήσουμε προαιρετικά ένα τμήμα else. Οι εντολές στο μπλοκ της else εκτελούνται μόνο όταν δεν συμβεί καμία εξαίρεση. Γι’ αυτό το λόγο βάζουμε το else μετά από όλες τις εξαιρέσεις.

>>> try:
         a=int(input('Δώσε τον αριθμό α '))
         print(a)
    except  ValueError:
         print(' Έδωσες  λάθος τιμή, χρειάζεται ακέραιος αριθμός')
    except:
         print(' Έγινε ένα απρόβλεπτο λάθος')
    else:
         print(' Η διαδικασία ολοκληρώθηκε επιτυχώς')
>>>

Στο προηγούμενο παράδειγμα οι εντολές του μπλοκ της else εκτελούνται μόνο όταν ο χρήστης πληκτρολογήσει μια ακέραια τιμή. Στην αντίθετη περίπτωση που εισάγουμε μια μη ακέραια τιμή, διεγείρεται μια εξαίρεση του τύπου ValueError και εκτελούνται οι εντολές του μπλοκ αυτής της εξαίρεσης. Αν συμβεί οποιοδήποτε άλλο σφάλμα, εκτελούνται οι εντολές του μπλοκ του τμήματος except χωρίς τύπο σφάλματος.

Στην εντολή try – except μπορούμε να χρησιμοποιήσουμε ένα τμήμα finally προαιρετικά. Οι εντολές του τμήματος finally εκτελούνται σε κάθε περίπτωση ανεξάρτητα αν συνέβη κάποια εξαίρεση (σφάλμα) ή όχι. Ακολουθεί ένα παράδειγμα:

try:
    a = int(input('Δώσε τον αριθμό a: '))
    b = int(input('Δώσε τον αριθμό b: '))
    r = a/b
except ZeroDivisionError:
    print('Διαίρεση με το μηδέν')
except (ValueError, TypeError, NameError):
    print('Μη αποδεκτά δεδομένα')
except:
    print('Εγινε ένα απρόβλεπτο σφάλμα')
else:
    print('a=%d,  b=%d, a/b=%f'%(a, b, r))
finally:
    print('Τέλος προγράμματος')

Στο πρώτο τμήμα except διαχειριζόμαστε την εξαίρεση της διαίρεσης με το μηδέν. Στο δεύτερο τμήμα except διαχειριζόμαστε τις εξαιρέσεις τριών τύπων σφαλμάτων. Αν συμβεί μια εξαίρεση ενός τύπου από αυτές τις εξαιρέσεις θα εκτελεστεί η print που ακολουθεί. Στο τρίτο τμήμα except διαχειριζόμαστε τους υπόλοιπους τύπους σφαλμάτων. Ακολουθεί το τμήμα else το οποίο τυπώνει το αποτέλεσμα της διαίρεσης. Στο τελευταίο τμήμα finally θα εκτελεστεί οπωσδήποτε η εντολή print αυτού του τμήματος ανεξάρτητα αν έχει προκληθεί σφάλμα (εξαίρεση) ή όχι.

Σε κάθε τμήμα except έχουμε την δυνατότητα να τοποθετήσουμε στο τέλος την έκφραση as παράμετρος η οποία δίνει περιγραφή του τύπου σφάλματος (εξαίρεση). Συγκεκριμένα ένα παράδειγμα σύνταξης είναι το ακόλουθο:

except  τύπος_εξαίρεσης  as  παράμετρος:
          print(παράμετρος)

Ένα παράδειγμα κώδικα είναι το εξής

>>> try:
        25/0
    except  ZeroDivisionError as error:
        print(error)

    division by zero

Άλλοι τύποι εξαιρέσεων είναι οι ακόλουθοι:

FileNotFoundError  Η Python δεν μπορεί να βρει ένα αρχείο
FloatingPointError   Σφάλμα σε διαχείριση αριθμών κινητής υποδιαστολής
ImportError  Η Python δεν μπόρεσε να βρει μια βιβλιοθήκη
OSError  Σφάλμα συστήματος υπολογιστή π.χ. γεμάτος δίσκος
OverflowError  Σφάλμα στο οποίο το αποτέλεσμα μιας πράξης δεν μπορεί να αναπαρασταθεί.