8.Σφάλματα και Εξαιρέσεις

Μέχρι τώρα τα μηνύματα σφαλμάτων (error messages) δεν ήταν περισσότερα από όσα αναφέρθηκαν, αλλά αν έχετε δοκιμάσει τα παραδείγματα, πιθανότατα έχετε δει μερικά. Υπάρχουν (τουλάχιστον) δύο διαφορετικά είδη σφαλμάτων:syntax errors (συντακτικά σφάλματα) καιexceptions (εξαιρέσεις).

8.1.Syntax Errors (Συντακτικά Σφάλματα)

Τα syntax errors, γνωστά και ως parsing errors, είναι ίσως το πιο συνηθισμένο είδος παραπόνου που λαμβάνετε ενώ εξακολουθείτε να μαθαίνετε Python:

>>>whileTrueprint('Hello world')  File"<stdin>", line1whileTrueprint('Hello world')^^^^^SyntaxError:invalid syntax

Ο αναλυτής επαναλαμβάνει την παραβατική γραμμή και εμφανίζει μικρά βέλη που δείχνουν προς το μέρος όπου που εντοπίστηκε το σφάλμα. Σημειώστε ότι αυτό δεν είναι πάντα το μέρος που πρέπει να διορθωθεί. Στο παράδειγμα, το σφάλμα εντοπίζεται στη συνάρτησηprint(), καθώς λείπει μια άνω και κάτω τελεία (':') ακριβώς πριν από αυτήν.

Το όνομα αρχείου (<stdin> στο παράδειγμά μας) και ο αριθμός γραμμής εκτυπώνονται, ώστε να να γνωρίζετε πού να ψάξετε σε περίπτωση που η είσοδος προήλθε από αρχείο.

8.2.Exceptions (Εξαιρέσεις)

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

>>>10*(1/0)Traceback (most recent call last):  File"<stdin>", line1, in<module>10*(1/0)~^~ZeroDivisionError:division by zero>>>4+spam*3Traceback (most recent call last):  File"<stdin>", line1, in<module>4+spam*3^^^^NameError:name 'spam' is not defined>>>'2'+2Traceback (most recent call last):  File"<stdin>", line1, in<module>'2'+2~~~~^~~TypeError:can only concatenate str (not "int") to str

Η τελευταία γραμμή του μηνύματος σφάλματος υποδεικνύει τι συνέβη. Οι εξαιρέσεις υπάρχουν σε διαφορετικούς τύπους και ο τύπος εκτυπώνεται ως μέρος του μηνύματος: οι τύποι στο παράδειγμα είναιZeroDivisionError,NameError καιTypeError. Η συμβολοσειρά που εκτυπώνεται ως τύπος εξαίρεσης είναι όνομα της ενσωματωμένης εξαίρεσης που προέκυψε. Αυτό ισχύει για όλες τις ενσωματωμένες (built-in) εξαιρέσεις, αλλά δεν χρειάζεται να ισχύει για εξαιρέσεις που ορίζονται από το χρήστη (αν και είναι μια χρήσιμη σύμβαση). Οι standard εξαιρέσεις είναι ενσωματωμένα (built-in) αναγνωριστικά (όχι δεσμευμένες λέξεις-κλειδιά).

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

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

ΤοBuilt-in Exceptions παραθέτει τις ενσωματωμένες εξαιρέσεις και τις έννοιές τους.

8.3.Διαχείριση Εξαιρέσεων

Είναι δυνατό να γραφτεί κώδικας που χειρίζεται επιλεγμένες εξαιρέσεις. Κοιτάξτε το ακόλουθο παράδειγμα, το οποίο ζητά από τον χρήστη να εισάγει έναν έγκυρο ακέραιο αριθμό, αλλά επιτρέπει στον χρήστη να διακόψει το πρόγραμμα (χρησιμοποιώνταςControl-C ή ό,τι υποστηρίζει το λειτουργικό σύστημα)· σημειώστε ότι μια διακοπή που δημιουργείται από τον χρήστη σηματοδοτείται κάνοντας raise την εξαίρεσηKeyboardInterrupt.

>>>whileTrue:...try:...x=int(input("Please enter a number: "))...break...exceptValueError:...print("Oops!  That was no valid number.  Try again...")...

Η δήλωσηtry λειτουργεί ως εξής.

  • Πρώτον, εκτελείται ηtry clause (η πρόταση(εις) μεταξύ των λέξεων-κλειδιώνtry andexcept).

  • Εάν δεν προκύψει εξαίρεση, ηexcept clause παραλείπεται και η εκτέλεση της πρότασηςtry ολοκληρώνεται.

  • Εάν παρουσιαστεί μια εξαίρεση κατά την εκτέλεση της πρότασηςtry, η υπόλοιπη πρόταση παραλείπεται. Στη συνέχεια, εάν ο τύπος της ταιριάζει με την εξαίρεση που ονομάζεται από τη λέξη-κλειδίexcept, ηexcept clause εκτελείται, και στη συνέχεια η εκτέλεση συνεχίζεται μετά το μπλοκ try/except.

  • Εάν προκύψει μια εξαίρεση που δεν ταιριάζει με την εξαίρεση που αναφέρεται στηνexcept clause, μεταβιβάζεται σε εξωτερικές εντολέςtry · εάν δεν βρεθεί κανένας χειριστής, είναι μιαunhandled exception και η εκτέλεση σταματά με μήνυμα σφάλματος.

Μια πρότασηtry μπορεί να έχει περισσότερες από μίαexcept clause, για να καθορίσει χειριστές για διαφορετικές εξαιρέσεις. Το πολύ ένας χειριστής θα εκτελεστεί. Οι χειριστές χειρίζονται μόνο εξαιρέσεις που εμφανίζονται στην αντίστοιχηtry clause, όχι σε άλλους χειριστές της ίδιας πρότασηςtry. Μιαexcept clause μπορεί να ονομάσει πολλαπλές εξαιρέσεις ως πλειάδα (tuple) σε παρένθεση, για παράδειγμα:

...except(RuntimeError,TypeError,NameError):...pass

Μια κλάση σε μια πρότασηexcept ταιριάζει με εξαιρέσεις που είναι στιγμιότυπα της ίδιας της κλάσης ή μιας από τις παραγόμενες κλάσεις της (αλλά όχι το αντίστροφο — μιαexcept clause που παραθέτει μια παράγωγη κλάση δεν ταιριάζει με τις παρουσίες των βασικών της κλάσεων). Για παράδειγμα, ο ακόλουθος κώδικας θα εκτυπώσει τα B, C, D με αυτή τη σειρά:

classB(Exception):passclassC(B):passclassD(C):passforclsin[B,C,D]:try:raisecls()exceptD:print("D")exceptC:print("C")exceptB:print("B")

Σημειώστε ότι εάν οιexcept clauses είχαν αντιστραφεί (με τοexceptB πρώτα), θα είχε εκτυπωθεί B, B, B — ενεργοποιείται η πρώτη αντιστοίχισηexcept clause.

Όταν προκύπτει μια εξαίρεση, μπορεί να έχει συσχετισμένες τιμές, γνωστές και ωςορίσματα της εξαίρεσης. Η παρουσία και οι τύποι των ορισμάτων εξαρτώνται από τον τύπο εξαίρεσης.

Τοexcept clause μπορεί να καθορίσει μια μεταβλητή μετά το όνομα της εξαίρεσης. Η μεταβλητή συνδέεται με το στιγμιότυπο της εξαίρεσης η οποία συνήθως έχει ένα χαρακτηριστικόargs που αποθηκεύει τα ορίσματα. Για ευκολία, οι ενσωματωμένοι (builtin) τύποι εξαίρεσης ορίζουν__str__() για να εκτυπώσετε όλα τα ορίσματα χωρίς ρητή πρόσβαση στο.args.

>>>try:...raiseException('spam','eggs')...exceptExceptionasinst:...print(type(inst))# the exception type...print(inst.args)# arguments stored in .args...print(inst)# __str__ allows args to be printed directly,...# but may be overridden in exception subclasses...x,y=inst.args# unpack args...print('x =',x)...print('y =',y)...<class 'Exception'>('spam', 'eggs')('spam', 'eggs')x = spamy = eggs

Η έξοδος της εξαίρεσης__str__() εκτυπώνεται ως το τελευταίο μέρος (“λεπτομέρεια”) του μηνύματος για μη χειριζόμενες εξαιρέσεις.

ΗBaseException είναι η κοινή βασική κλάση όλων των εξαιρέσεων. Μια από τις υποκατηγορίες της,Exception, είναι η βασική κλάση όλων των μη μοιραίων εξαιρέσεων. Εξαιρέσεις που δεν είναι υποκλάσεις τουException δεν αντιμετωπίζονται συνήθως, επειδή χρησιμοποιούνται για να υποδείξουν ότι το πρόγραμμα πρέπει να τερματιστεί. Περιλαμβάνουν τοSystemExit το οποίο αυξάνεται από τοsys.exit() και τοKeyboardInterrupt το οποίο γίνεται raise όταν ο χρήστης επιθυμεί να διακόψει την εκτέλεση του προγράμματος.

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

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

importsystry:f=open('myfile.txt')s=f.readline()i=int(s.strip())exceptOSErroraserr:print("OS error:",err)exceptValueError:print("Could not convert data to an integer.")exceptExceptionaserr:print(f"Unexpected{err=},{type(err)=}")raise

Η πρότασηtryexcept έχει ένα προαιρετικόelse clause, το οποίο, όταν υπάρχει, πρέπει να ακολουθεί όλες τιςexcept clauses. Είναι χρήσιμο για κώδικα που πρέπει να εκτελεστεί εάν τοtry clause δεν κάνει raise μια εξαίρεση. Για παράδειγμα:

forarginsys.argv[1:]:try:f=open(arg,'r')exceptOSError:print('cannot open',arg)else:print(arg,'has',len(f.readlines()),'lines')f.close()

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

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

>>>defthis_fails():...x=1/0...>>>try:...this_fails()...exceptZeroDivisionErroraserr:...print('Handling run-time error:',err)...Handling run-time error: division by zero

8.4.Raising Εξαιρέσεων

Η δήλωσηraise επιτρέπει στον προγραμματιστή να αναγκάσει να εμφανιστεί μια καθορισμένη εξαίρεση. Για παράδειγμα:

>>>raiseNameError('HiThere')Traceback (most recent call last):  File"<stdin>", line1, in<module>raiseNameError('HiThere')NameError:HiThere

Το μοναδικό όρισμα στοraise υποδεικνύει την εξαίρεση που πρέπει να γίνει raise. Αυτή πρέπει να είναι είτε μια παρουσία εξαίρεσης ή μια εξαίρεση κλάση (μια κλάση που προέρχεται απόBaseException, όπωςException ή μία από τις υποκλάσεις της). Εάν περάσει μια κλάση εξαίρεσης, θα δημιουργηθεί σιωπηρά καλώντας τον constructor της χωρίς ορίσματα:

κάνειraiseέναValueError# συντομογραφία για το 'raise ValueError()'

Εάν πρέπει να προσδιορίσετε εάν έχει εγγραφεί μια εξαίρεση, αλλά δεν σκοπεύετε να τη χειριστείτε, μια απλούστερη μορφή της δήλωσηςraise σας επιτρέπει να κάνετε ξανά raise την εξαίρεση:

>>>try:...raiseNameError('HiThere')...exceptNameError:...print('An exception flew by!')...raise...An exception flew by!Traceback (most recent call last):  File"<stdin>", line2, in<module>raiseNameError('HiThere')NameError:HiThere

8.5.Αλυσιδωτές Εξαιρέσεις

Εάν παρουσιαστεί μια μη χειριζόμενη (unhandled) εξαίρεση μέσα σε μια ενότηταexcept, θα επισυνάψει την εξαίρεση που θα χειριστεί και θα συμπεριληφθεί στο μήνυμα σφάλματος:

>>>try:...open("database.sqlite")...exceptOSError:...raiseRuntimeError("unable to handle error")...Traceback (most recent call last):  File"<stdin>", line2, in<module>open("database.sqlite")~~~~^^^^^^^^^^^^^^^^^^^FileNotFoundError:[Errno 2] No such file or directory: 'database.sqlite'During handling of the above exception, another exception occurred:Traceback (most recent call last):  File"<stdin>", line4, in<module>raiseRuntimeError("unable to handle error")RuntimeError:unable to handle error

Για να υποδείξετε ότι μια εξαίρεση είναι άμεση συνέπεια μιας άλλης, η πρότασηraise επιτρέπει μια προαιρετική πρότασηfrom:

# Το exc πρέπει να είναι παράδειγμα εξαίρεσης ή None.κάνειraiseτοRuntimeErrorαπόexc

Αυτό μπορεί να είναι χρήσιμο όταν μετασχηματίζεται εξαιρέσεις. Για παράδειγμα:

>>>deffunc():...raiseConnectionError...>>>try:...func()...exceptConnectionErrorasexc:...raiseRuntimeError('Failed to open database')fromexc...Traceback (most recent call last):  File"<stdin>", line2, in<module>func()~~~~^^  File"<stdin>", line2, infuncConnectionErrorThe above exception was the direct cause of the following exception:Traceback (most recent call last):  File"<stdin>", line4, in<module>raiseRuntimeError('Failed to open database')fromexcRuntimeError:Failed to open database

Επιτρέπει επίσης την απενεργοποίηση της αυτόματης αλυσίδας εξαιρέσεων χρησιμοποιώνταςfromNone idiom:

>>>try:...open('database.sqlite')...exceptOSError:...raiseRuntimeErrorfromNone...Traceback (most recent call last):  File"<stdin>", line4, in<module>raiseRuntimeErrorfromNoneRuntimeError

Για περισσότερες πληροφορίες σχετικά με την μηχανική αλυσίδων, δείτεBuilt-in Exceptions.

8.6.Εξαιρέσεις που καθορίζονται από το χρήστη

Τα προγράμματα μπορούν να ονομάσουν τις δικές τους εξαιρέσεις δημιουργώντας μια νέα κλάση εξαιρέσεων (δείτεΚλάσεις για περισσότερα σχετικά με τις κλάσεις Python). Οι εξαιρέσεις θα πρέπει συνήθως να προέρχονται από την κλάσηException, είτε άμεσα είτε έμμεσα.

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

Οι περισσότερες εξαιρέσεις ορίζονται με ονόματα που τελειώνουν σε «Error», παρόμοια με την ονομασία των τυπικών εξαιρέσεων.

Πολλά standard modules ορίζουν τις δικές τους εξαιρέσεις για την αναφορά σφαλμάτων που μπορεί να προκύψουν σε συναρτήσεις που ορίζουν.

8.7.Καθορισμός ενεργειών καθαρισμού

Η δήλωσηtry έχει μια άλλη προαιρετική πρόταση που προορίζεται να ορίσει ενέργειες καθαρισμού που πρέπει να εκτελεστούν υπό οποιεσδήποτε συνθήκες. Για παράδειγμα:

>>>try:...raiseKeyboardInterrupt...finally:...print('Goodbye, world!')...Goodbye, world!Traceback (most recent call last):  File"<stdin>", line2, in<module>raiseKeyboardInterruptKeyboardInterrupt

Εάν υπάρχει μια πρότασηfinally, η πρότασηfinally θα εκτελεστεί ως η τελευταία εργασία πριν από την ολοκλήρωση της πρότασηςtry. Η πρότασηfinally εκτελείται είτε όχι η πρότασηtry παράγει μια εξαίρεση. Τα ακόλουθα σημεία συζητούν πιο περίπλοκες περιπτώσεις όταν εμφανίζεται μια εξαίρεση:

  • Εάν παρουσιαστεί μια εξαίρεση κατά την εκτέλεση της πρότασηςtry, η εξαίρεση μπορεί να αντιμετωπιστεί από μια πρότασηexcept, Εάν η εξαίρεση δεν αντιμετωπίζεται από μια πρότασηexcept, η εξαίρεση γίνεται ξανά raise μετά την εκτέλεση της πρότασηςfinally.

  • Μια εξαίρεση θα μπορούσε να προκύψει κατά την εκτέλεση μιας πρότασηςexcept ήelse. Και πάλι, η εξαίρεση τίθεται ξανά μετά την εκτέλεση της πρότασηςfinally.

  • Εάν η πρότασηfinally εκτελέσει μια πρότασηbreak,continue ήreturn, οι εξαιρέσεις δεν γίνονται raise εκ νέου. Αυτό μπορεί να προκαλέσει σύγχυση και ως εκ τούτου αποθαρρύνεται. Από την έκδοση 3.14, ο μεταγλωττιστής εκπέμπει μιαSyntaxWarning για αυτό (δείτεPEP 765).

  • Εάν η πρότασηtry φτάσει σε μια δήλωσηbreak,continue ήreturn, η πρότασηfinally θα εκτελεστεί ακριβώς πριν από ταbreak,continue orreturn της εκτέλεσης της δήλωσης.

  • Εάν μια πρότασηfinally περιλαμβάνει μια δήλωσηreturn, η τιμή που επιστρέφεται θα είναι αυτή από την πρότασηfinally της δήλωσης τηςreturn, και όχι η τιμή από τη δήλωσηtry της πρότασηςreturn. Αυτό μπορεί να προκαλέσει σύγχυση και επομένως αποθαρρύνεται. Από την έκδοση 3.14, ο μεταγλωττιστής εκπέμπει έναSyntaxWarning για αυτό (δείτεPEP 765).

Για παράδειγμα:

>>>defbool_return():...try:...returnTrue...finally:...returnFalse...>>>bool_return()False

Ένα πιο περίπλοκο παράδειγμα:

>>>defdivide(x,y):...try:...result=x/y...exceptZeroDivisionError:...print("division by zero!")...else:...print("result is",result)...finally:...print("executing finally clause")...>>>divide(2,1)result is 2.0executing finally clause>>>divide(2,0)division by zero!executing finally clause>>>divide("2","1")executing finally clauseTraceback (most recent call last):  File"<stdin>", line1, in<module>divide("2","1")~~~~~~^^^^^^^^^^  File"<stdin>", line3, individeresult=x/y~~^~~TypeError:unsupported operand type(s) for /: 'str' and 'str'

Όπως μπορείτε να δείτε, η πρότασηfinally εκτελείται σε οποιαδήποτε περίπτωση. ΤοTypeError που δημιουργείται με τη διαίρεση δύο συμβολοσειρών δεν χειρίζεται από την πρότασηexcept και επομένως γίνεται ξανά raise μετά την εκτέλεση του όρουfinally.

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

8.8.Προκαθορισμένες ενέργειες καθαρισμού

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

forlineinopen("myfile.txt"):print(line,end="")

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

withopen("myfile.txt")asf:forlineinf:print(line,end="")

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

8.9.Raising και Χειρισμός Πολλαπλών Άσχετων Εξαιρέσεων

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

Η ενσωματωμένη (builtin)ExceptionGroup αναδιπλώνει μια λίστα με παρουσίες εξαιρέσεων ώστε να μπορούν να αυξηθούν μαζί. Είναι μια εξαίρεση από μόνη της, επομένως μπορεί να συλληφθεί όπως κάθε άλλη εξαίρεση.

>>>deff():...excs=[OSError('error 1'),SystemError('error 2')]...raiseExceptionGroup('there were problems',excs)...>>>f()  + Exception Group Traceback (most recent call last):  |   File "<stdin>", line 1, in <module>  |     f()  |     ~^^  |   File "<stdin>", line 3, in f  |     raise ExceptionGroup('there were problems', excs)  | ExceptionGroup: there were problems (2 sub-exceptions)  +-+---------------- 1 ----------------    | OSError: error 1    +---------------- 2 ----------------    | SystemError: error 2    +------------------------------------>>>try:...f()...exceptExceptionase:...print(f'caught{type(e)}: e')...caught <class 'ExceptionGroup'>: e>>>

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

>>>deff():...raiseExceptionGroup(..."group1",...[...OSError(1),...SystemError(2),...ExceptionGroup(..."group2",...[...OSError(3),...RecursionError(4)...]...)...]...)...>>>try:...f()...except*OSErrorase:...print("There were OSErrors")...except*SystemErrorase:...print("There were SystemErrors")...There were OSErrorsThere were SystemErrors  + Exception Group Traceback (most recent call last):  |   File "<stdin>", line 2, in <module>  |     f()  |     ~^^  |   File "<stdin>", line 2, in f  |     raise ExceptionGroup(  |     ...<12 lines>...  |     )  | ExceptionGroup: group1 (1 sub-exception)  +-+---------------- 1 ----------------    | ExceptionGroup: group2 (1 sub-exception)    +-+---------------- 1 ----------------      | RecursionError: 4      +------------------------------------>>>

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

>>>excs=[]...fortestintests:...try:...test.run()...exceptExceptionase:...excs.append(e)...>>>ifexcs:...raiseExceptionGroup("Test Failures",excs)...

8.10.Εμπλουτίζοντας τις Εξαιρέσεις με Σημειώσεις

Όταν δημιουργείται μια εξαίρεση προκειμένου να γίνει raise, συνήθως αρχικοποιείται με πληροφορίες που περιγράφουν το σφάλμα που έχει προκύψει. Υπάρχουν περιπτώσεις όπου είναι χρήσιμο να προστεθούν πληροφορίες μετά την σύλληψη της εξαίρεσης. Για το σκοπό αυτό, οι εξαιρέσεις έχουνε μια μέθοδοadd_note(note) που δέχεται μια συμβολοσειρά και την προσθέτει στη λίστα σημειώσεων της εξαίρεσης. Η standard απόδοση παρακολούθησης περιλαμβάνει όλες τις σημειώσεις, με τη σειρά που προστέθηκα, μετά την εξαίρεση.

>>>try:...raiseTypeError('bad type')...exceptExceptionase:...e.add_note('Add some information')...e.add_note('Add some more information')...raise...Traceback (most recent call last):  File"<stdin>", line2, in<module>raiseTypeError('bad type')TypeError:bad typeAdd some informationAdd some more information>>>

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

>>>deff():...raiseOSError('operation failed')...>>>excs=[]>>>foriinrange(3):...try:...f()...exceptExceptionase:...e.add_note(f'Happened in Iteration{i+1}')...excs.append(e)...>>>raiseExceptionGroup('We have some problems',excs)  + Exception Group Traceback (most recent call last):  |   File "<stdin>", line 1, in <module>  |     raise ExceptionGroup('We have some problems', excs)  | ExceptionGroup: We have some problems (3 sub-exceptions)  +-+---------------- 1 ----------------    | Traceback (most recent call last):    |   File "<stdin>", line 3, in <module>    |     f()    |     ~^^    |   File "<stdin>", line 2, in f    |     raise OSError('operation failed')    | OSError: operation failed    | Happened in Iteration 1    +---------------- 2 ----------------    | Traceback (most recent call last):    |   File "<stdin>", line 3, in <module>    |     f()    |     ~^^    |   File "<stdin>", line 2, in f    |     raise OSError('operation failed')    | OSError: operation failed    | Happened in Iteration 2    +---------------- 3 ----------------    | Traceback (most recent call last):    |   File "<stdin>", line 3, in <module>    |     f()    |     ~^^    |   File "<stdin>", line 2, in f    |     raise OSError('operation failed')    | OSError: operation failed    | Happened in Iteration 3    +------------------------------------>>>