Τρόπος χειρισμού εξαιρέσεων στο Python: Μια λεπτομερής οπτική εισαγωγή

Καλως ΗΡΘΑΤΕ! Σε αυτό το άρθρο, θα μάθετε πώς να χειρίζεστε εξαιρέσεις στο Python.

Συγκεκριμένα, θα καλύψουμε:

  • Εξαιρέσεις
  • Ο σκοπός του χειρισμού εξαιρέσεων
  • Η ρήτρα δοκιμής
  • Η ρήτρα εκτός
  • Η άλλη ρήτρα
  • Η επιτέλους ρήτρα
  • Πώς να αυξήσετε τις εξαιρέσεις

Είσαι έτοιμος? Ας ξεκινήσουμε! ?

1️⃣ Εισαγωγή στις εξαιρέσεις

Θα ξεκινήσουμε με εξαιρέσεις:

  • Τι είναι;
  • Γιατί είναι σχετικοί;
  • Γιατί να τα χειριστείτε;

Σύμφωνα με την τεκμηρίωση της Python:

Τα σφάλματα που εντοπίστηκαν κατά την εκτέλεση ονομάζονται εξαιρέσεις και δεν είναι ανεπιφύλακτα θανατηφόρα.

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

Πιθανότατα τα έχετε δει κατά τη διάρκεια των προγραμμάτων προγραμματισμού σας.

Εάν έχετε προσπαθήσει ποτέ να διαιρέσετε με μηδέν στο Python, πρέπει να έχετε δει αυτό το μήνυμα σφάλματος:

>>> a = 5/0 Traceback (most recent call last): File "", line 1, in  a = 5/0 ZeroDivisionError: division by zero

Εάν προσπαθήσατε να ευρετηριάσετε μια συμβολοσειρά με μη έγκυρο ευρετήριο, λάβατε σίγουρα αυτό το μήνυμα σφάλματος:

>>> a = "Hello, World" >>> a[456] Traceback (most recent call last): File "", line 1, in  a[456] IndexError: string index out of range

Αυτά είναι παραδείγματα εξαιρέσεων.

? Κοινές εξαιρέσεις

Υπάρχουν πολλοί διαφορετικοί τύποι εξαιρέσεων, και αυτές αναφέρονται σε συγκεκριμένες καταστάσεις. Μερικές από τις εξαιρέσεις που πιθανότατα θα δείτε καθώς εργάζεστε στα έργα σας είναι:

  • IndexError - αυξάνεται όταν προσπαθείτε να ευρετηριάσετε μια λίστα, πλειάδα ή συμβολοσειρά πέρα ​​από τα επιτρεπόμενα όρια. Για παράδειγμα:
>>> num = [1, 2, 6, 5] >>> num[56546546] Traceback (most recent call last): File "", line 1, in  num[56546546] IndexError: list index out of range
  • KeyError - αυξάνεται όταν προσπαθείτε να αποκτήσετε πρόσβαση στην τιμή ενός κλειδιού που δεν υπάρχει σε ένα λεξικό. Για παράδειγμα:
>>> students = {"Nora": 15, "Gino": 30} >>> students["Lisa"] Traceback (most recent call last): File "", line 1, in  students["Lisa"] KeyError: 'Lisa'
  • NameError - εμφανίζεται όταν δεν υπάρχει όνομα που αναφέρετε στον κώδικα. Για παράδειγμα:
>>> a = b Traceback (most recent call last): File "", line 1, in  a = b NameError: name 'b' is not defined
  • TypeError - εμφανίζεται όταν εφαρμόζεται μια λειτουργία ή συνάρτηση σε αντικείμενο ακατάλληλου τύπου. Για παράδειγμα:
>>> (5, 6, 7) * (1, 2, 3) Traceback (most recent call last): File "", line 1, in  (5, 6, 7) * (1, 2, 3) TypeError: can't multiply sequence by non-int of type 'tuple'
  • ZeroDivisionError - αυξάνεται όταν προσπαθείτε να διαιρέσετε με μηδέν.
>>> a = 5/0 Traceback (most recent call last): File "", line 1, in  a = 5/0 ZeroDivisionError: division by zero

? Συμβουλές: Για να μάθετε περισσότερα σχετικά με άλλους τύπους ενσωματωμένων εξαιρέσεων, ανατρέξτε σε αυτό το άρθρο στην τεκμηρίωση Python.

? Ανατομία εξαίρεσης

Είμαι βέβαιος ότι πρέπει να έχετε παρατηρήσει ένα γενικό μοτίβο σε αυτά τα μηνύματα σφάλματος. Ας αναλύσουμε το κομμάτι της γενικής δομής τους:

Πρώτον, βρίσκουμε αυτή τη γραμμή (δείτε παρακάτω). Ένα ίχνος επιστροφής είναι βασικά μια λίστα που περιγράφει λεπτομερώς τις κλήσεις λειτουργιών που πραγματοποιήθηκαν πριν από την εξαίρεση.

Το traceback σάς βοηθά κατά τη διαδικασία εντοπισμού σφαλμάτων, επειδή μπορείτε να αναλύσετε την ακολουθία των κλήσεων συνάρτησης που οδήγησαν στην εξαίρεση:

Traceback (most recent call last):

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

File "", line 1, in  a - 5/0

? Συμβουλή: Εάν η γραμμή που έθεσε την εξαίρεση ανήκει σε μια συνάρτηση, αντικαθίσταται από το όνομα της συνάρτησης.

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

NameError: name 'a' is not defined

2️⃣ Χειρισμός εξαιρέσεων: Σκοπός & πλαίσιο

Μπορείτε να ρωτήσετε: γιατί θα ήθελα να χειριστώ τις εξαιρέσεις; Γιατί είναι χρήσιμο αυτό για μένα; Με τον χειρισμό εξαιρέσεων, μπορείτε να παρέχετε μια εναλλακτική ροή εκτέλεσης για να αποφύγετε την απροσδόκητη διακοπή του προγράμματος.

? Παράδειγμα: Είσοδος χρήστη

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

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

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

Perhaps you could display a descriptive message asking the user to enter a valid input, or you could provide a default value for the input. Depending on the context, you can choose what to do when this happens, and this is the magic of error handling. It can save the day when unexpected things happen. ⭐️

? What Happens Behind the Scenes?

Basically, when we handle an exception, we are telling the program what to do if the exception is raised. In that case, the "alternative" flow of execution will come to the rescue. If no exceptions are raised, the code will run as expected.

3️⃣ Time to Code: The try ... except Statement

Now that you know what exceptions are and why you should we handle them, we will start diving into the built-in tools that the Python languages offers for this purpose.

First, we have the most basic statement: try ... except.

Let's illustrate this with a simple example. We have this small program that asks the user to enter the name of a student to display his/her age:

students = {"Nora": 15, "Gino": 30} def print_student_age(): name = input("Please enter the name of the student: ") print(students[name]) print_student_age()

Notice how we are not validating user input at the moment, so the user might enter invalid values (names that are not in the dictionary) and the consequences would be catastrophic because the program would crash if a KeyError is raised:

# User Input Please enter the name of the student: "Daniel" # Error Message Traceback (most recent call last): File "", line 15, in  print_student_age() File "", line 13, in print_student_age print(students[name]) KeyError: '"Daniel"'

? Syntax

We can handle this nicely using try ... except. This is the basic syntax:

In our example, we would add the try ... except statement within the function. Let's break this down piece by piece:

students = {"Nora": 15, "Gino": 30} def print_student_age(): while True: try: name = input("Please enter the name of the student: ") print(students[name]) break except: print("This name is not registered") print_student_age()

If we "zoom in", we see the try ... except statement:

try: name = input("Please enter the name of the student: ") print(students[name]) break except: print("This name is not registered")
  • When the function is called, the try clause will run. If no exceptions are raised, the program will run as expected.
  • But if an exception is raised in the try clause, the flow of execution will immediately jump to the except clause to handle the exception.

? Note: This code is contained within a while loop to continue asking for user input if the value is invalid. This is an example:

Please enter the name of the student: "Lulu" This name is not registered Please enter the name of the student: 

This is great, right? Now we can continue asking for user input if the value is invalid.

At the moment, we are handling all possible exceptions with the same except clause. But what if we only want to handle a specific type of exception? Let's see how we could do this.

? Catching Specific Exceptions

Since not all types of exceptions are handled in the same way, we can specify which exceptions we would like to handle with this syntax:

This is an example. We are handling the ZeroDivisionError exception in case the user enters zero as the denominator:

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) print(a / b) except ZeroDivisionError: print("Please enter a valid denominator.") divide_integers()

Αυτό θα ήταν το αποτέλεσμα:

# First iteration Please enter the numerator: 5 Please enter the denominator: 0 Please enter a valid denominator. # Second iteration Please enter the numerator: 5 Please enter the denominator: 2 2.5

Το χειριζόμαστε σωστά. Αλλά ... αν προκύψει άλλος τύπος εξαίρεσης, το πρόγραμμα δεν θα το χειριστεί με χαρά.

Εδώ έχουμε ένα παράδειγμα ValueError επειδή μία από τις τιμές είναι float, όχι int:

Please enter the numerator: 5 Please enter the denominator: 0.5 Traceback (most recent call last): File "", line 53, in  divide_integers() File "", line 47, in divide_integers b = int(input("Please enter the denominator: ")) ValueError: invalid literal for int() with base 10: '0.5'

Μπορούμε να προσαρμόσουμε τον τρόπο χειρισμού διαφορετικών τύπων εξαιρέσεων.

? Πολλαπλές εκτός από ρήτρες

Για να γίνει αυτό, πρέπει να προσθέσουμε πολλές exceptρήτρες για να χειριστούμε διαφορετικούς τύπους εξαιρέσεων με διαφορετικό τρόπο.

Σύμφωνα με την τεκμηρίωση Python:

Μια δήλωση δοκιμής μπορεί να έχει περισσότερα από ένα εκτός της ρήτρας , για να καθορίσει χειριστές για διαφορετικές εξαιρέσεις. Θα εκτελεστεί το πολύ ένας χειριστής .

Σε αυτό το παράδειγμα, έχουμε δύο εκτός από ρήτρες. Ένας από αυτούς χειρίζεται το ZeroDivisionError και ο άλλος χειρίζεται το ValueError, τους δύο τύπους εξαιρέσεων που θα μπορούσαν να προκύψουν σε αυτό το μπλοκ δοκιμής.

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) print(a / b) except ZeroDivisionError: print("Please enter a valid denominator.") except ValueError: print("Both values have to be integers.") divide_integers() 

? Συμβουλή: Πρέπει να καθορίσετε ποιοι τύποι εξαιρέσεων ενδέχεται να δημιουργηθούν στο μπλοκ δοκιμών για να τις χειριστείτε σωστά.

? Πολλαπλές εξαιρέσεις, μία εκτός της ρήτρας

Μπορείτε επίσης να επιλέξετε να χειριστείτε διαφορετικούς τύπους εξαιρέσεων με τον ίδιο, εκτός από τον όρο.

Σύμφωνα με την τεκμηρίωση Python:

Μια ρήτρα εκτός μπορεί να ονομάσει πολλές εξαιρέσεις ως παρένθεση.

Αυτό είναι ένα παράδειγμα όπου εντοπίζουμε δύο εξαιρέσεις (ZeroDivisionError και ValueError) με την ίδια exceptρήτρα:

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) print(a / b) except (ZeroDivisionError, ValueError): print("Please enter valid integers.") divide_integers()

Η έξοδος θα ήταν η ίδια για τους δύο τύπους εξαιρέσεων, επειδή αντιμετωπίζονται από το ίδιο, εκτός από τον όρο:

Please enter the numerator: 5 Please enter the denominator: 0 Please enter valid integers.
Please enter the numerator: 0.5 Please enter valid integers. Please enter the numerator: 

? Χειρισμός εξαιρέσεων που προκύπτουν από συναρτήσεις που αναφέρονται στην ρήτρα δοκιμής

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

Σύμφωνα με την τεκμηρίωση Python:

Οι χειριστές εξαίρεσης δεν χειρίζονται μόνο εξαιρέσεις, εάν εμφανιστούν αμέσως στον όρο δοκιμής, αλλά και εάν εμφανίζονται σε συναρτήσεις που ονομάζονται (ακόμη και έμμεσα) στη ρήτρα δοκιμής.

Ας δούμε ένα παράδειγμα για να το δείξουμε αυτό:

def f(i): try: g(i) except IndexError: print("Please enter a valid index") def g(i): a = "Hello" return a[i] f(50)

We have the f function and the g function. f calls g in the try clause. With the argument 50, g will raise an IndexError because the index 50 is not valid for the string a.

But g itself doesn't handle the exception. Notice how there is no try ... except statement in the g function. Since it doesn't handle the exception, it "sends" it to f to see if it can handle it, as you can see in the diagram below:

Since f does know how to handle an IndexError, the situation is handled gracefully and this is the output:

Please enter a valid index

? Note: If f had not handled the exception, the program would have ended abruptly with the default error message for an IndexError.

? Πρόσβαση σε συγκεκριμένες λεπτομέρειες εξαιρέσεων

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

Σύμφωνα με την τεκμηρίωση Python:

Η ρήτρα εκτός μπορεί να καθορίσει μια μεταβλητή μετά το όνομα εξαίρεσης . Η μεταβλητή συνδέεται με μια παρουσία εξαίρεσης με τα ορίσματα που είναι αποθηκευμένα στο instance.args.

Εδώ έχουμε ένα παράδειγμα (βλ. Παρακάτω) αν αναθέσαμε την παρουσία ZeroDivisionErrorστη μεταβλητή e. Στη συνέχεια, μπορούμε να χρησιμοποιήσουμε αυτήν τη μεταβλητή εντός της ρήτρας εξαίρεσης για πρόσβαση στον τύπο της εξαίρεσης, το μήνυμά της και τα ορίσματα.

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) print(a / b) # Here we assign the exception to the variable e except ZeroDivisionError as e: print(type(e)) print(e) print(e.args) divide_integers()

Η αντίστοιχη έξοδος θα ήταν:

Please enter the numerator: 5 Please enter the denominator: 0 # Type  # Message division by zero # Args ('division by zero',)

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

4️⃣ Τώρα ας προσθέσουμε: Η ρήτρα "other"

Η elseρήτρα είναι προαιρετική, αλλά είναι ένα εξαιρετικό εργαλείο γιατί μας επιτρέπει να εκτελέσουμε κώδικα που θα πρέπει να εκτελείται μόνο εάν δεν έχουν προκύψει εξαιρέσεις στη ρήτρα δοκιμής.

Σύμφωνα με την τεκμηρίωση Python:

Η δήλωση tryexceptέχει μια προαιρετική άλλη ρήτρα , η οποία, όταν υπάρχει, πρέπει να ακολουθεί όλα εκτός από τις ρήτρες. Είναι χρήσιμο για κώδικα που πρέπει να εκτελεστεί εάν η ρήτρα δοκιμής δεν δημιουργεί εξαίρεση.

Ακολουθεί ένα παράδειγμα χρήσης της elseρήτρας:

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) result = a / b except (ZeroDivisionError, ValueError): print("Please enter valid integers. The denominator can't be zero") else: print(result) divide_integers()

Εάν δεν δημιουργηθεί καμία εξαίρεση, εκτυπώνεται το αποτέλεσμα:

Please enter the numerator: 5 Please enter the denominator: 5 1.0

Αλλά αν προκύψει εξαίρεση, το αποτέλεσμα δεν εκτυπώνεται:

Please enter the numerator: 5 Please enter the denominator: 0 Please enter valid integers. The denominator can't be zero

? Συμβουλή: Σύμφωνα με την τεκμηρίωση Python:

Η χρήση της elseρήτρας είναι καλύτερη από την προσθήκη πρόσθετου κώδικα στην tryπρόταση επειδή αποφεύγει κατά λάθος την εξαίρεση που δεν δημιουργήθηκε από τον κώδικα που προστατεύεται από τη δήλωση tryexcept.

5️⃣ Η ρήτρα «επιτέλους»

Η τελευταία ρήτρα είναι η τελευταία ρήτρα αυτής της ακολουθίας. Είναι προαιρετικό , αλλά αν το συμπεριλάβετε, πρέπει να είναι ο τελευταίος όρος της ακολουθίας. Η finallyρήτρα εκτελείται πάντοτε , ακόμα κι αν έχει δημιουργηθεί εξαίρεση στη ρήτρα δοκιμής.  

Σύμφωνα με την τεκμηρίωση Python:

Εάν υπάρχει μια finallyρήτρα, η finallyρήτρα θα εκτελεστεί ως η τελευταία εργασία πριν tryολοκληρωθεί η δήλωση. Η finallyρήτρα τρέχει εάν η tryδήλωση παράγει μια εξαίρεση.

Ο τελικός όρος χρησιμοποιείται συνήθως για την εκτέλεση ενεργειών "καθαρισμού" που πρέπει πάντα να ολοκληρώνονται. Για παράδειγμα, εάν εργαζόμαστε με ένα αρχείο στη ρήτρα δοκιμής, θα πρέπει πάντα να κλείσουμε το αρχείο, ακόμα και αν δημιουργήθηκε μια εξαίρεση όταν εργαζόμαστε με τα δεδομένα.

Εδώ είναι ένα παράδειγμα της τελικής ρήτρας:

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) result = a / b except (ZeroDivisionError, ValueError): print("Please enter valid integers. The denominator can't be zero") else: print(result) finally: print("Inside the finally clause") divide_integers()

Αυτή είναι η έξοδος όταν δεν προέκυψαν εξαιρέσεις:

Please enter the numerator: 5 Please enter the denominator: 5 1.0 Inside the finally clause

Αυτή είναι η έξοδος όταν προέκυψε μια εξαίρεση:

Please enter the numerator: 5 Please enter the denominator: 0 Please enter valid integers. The denominator can't be zero Inside the finally clause

Παρατηρήστε πώς τρέχει πάντα η finallyρήτρα .

❗️Important: remember that the else clause and the finally clause are optional, but if you decide to include both, the finally clause has to be the last clause in the sequence.

6️⃣ Raising Exceptions

Now that you know how to handle exceptions in Python, I would like to share with you this helpful tip: you can also choose when to raise exceptions in your code.

This can be helpful for certain scenarios. Let's see how you can do this:

This line will raise a ValueError with a custom message.

Here we have an example (see below) of a function that prints the value of the items of a list or tuple, or the characters in a string. But you decided that you want the list, tuple, or string to be of length 5. You start the function with an if statement that checks if the length of the argument data is 5. If it isn't, a ValueError exception is raised:

def print_five_items(data): if len(data) != 5: raise ValueError("The argument must have five elements") for item in data: print(item) print_five_items([5, 2])

The output would be:

Traceback (most recent call last): File "", line 122, in  print_five_items([5, 2]) File "", line 117, in print_five_items raise ValueError("The argument must have five elements") ValueError: The argument must have five elements

Notice how the last line displays the descriptive message:

ValueError: The argument must have five elements

You can then choose how to handle the exception with a try ... except statement. You could add an else clause and/or a finally clause. You can customize it to fit your needs.

? Helpful Resources

  • Exceptions
  • Handling Exceptions
  • Defining Clean-up Actions

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

⭐️ Μπορείτε να απολαύσετε τα άλλα δωρεάν άρθρα μουCodeCamp / ειδήσεων:

  • Το @property Decorator στο Python: Οι θήκες χρήσης, τα πλεονεκτήματα και η σύνταξη
  • Δομές δεδομένων 101: Γραφήματα - Μια οπτική εισαγωγή για αρχάριους
  • Δομές δεδομένων 101: Πίνακες - Μια οπτική εισαγωγή για αρχάριους