ΜΥΥ105: Εισαγωγή στον Προγραμματισμό Σφάλματα, εξαιρέσεις, εκσφαλμάτωση Χειμερινό Εξάμηνο 2016
Τύποι Σφαλμάτων Συντακτικά λάθη (syntax errors) Λάθη κατά την εκτέλεση (run-time errors) Λογικά σφάλματα 2
Συντακτικά Λάθη Είναι σημαντικό να καταλάβουμε το συντακτικό λάθος και να το διορθώσουμε break.py from math import sqrt for n in range(99, 0, -1): root = sqrt(n) if root == int(root): print (n) break python3 break.py File "break.py", line 6 break ^ IndentationError: unindent does not match any outer indentation level Ο interpreter της Python μας λέει σε ποια γραμμή είναι το λάθος Επίσης το μήνυμα μπορεί να μας δίνει επιπλέον πληροφορία για το λάθος. 3
Συντακτικά Λάθη Είναι σημαντικό να καταλάβουμε το συντακτικό λάθος και να το διορθώσουμε test.py suits = ['\u2660', '\u2661', '\u2662', '\u2663'] print((suits) x = 5 python3 test.py File "test.py", line 3 x = 5 ^ SyntaxError: invalid syntax Σε κάποια λάθη, το μήνυμα μπορεί να φαίνεται ότι είναι για λάθος γραμμή. Αυτή είναι η γραμμή στην οποία ο interpreter δεν μπορεί πλέον να ολοκληρώσει μια σωστή εντολή 4
Συντακτικά Λάθη Είναι σημαντικό να καταλάβουμε το συντακτικό λάθος και να το διορθώσουμε >>> (3+4] SyntaxError: invalid syntax >>> if x==5 SyntaxError: invalid syntax >>> print 'hello' SyntaxError: invalid syntax >>> lst = [1;2;3] SyntaxError: invalid syntax >>> for i in range(10): print(i) SyntaxError: expected an indented block 5
Λάθη κατά την εκτέλεση Δεν υπάρχει συντακτικό λάθος, αλλά το πρόγραμμά μας τερματίζει γιατί μπαίνει σε μια μη εγκυρη κατάσταση. >>> 4/0 Traceback (most recent call last): File "<pyshell#43>", line 1, in <module> 4/0 ZeroDivisionError: division by zero >>> lst = [1,2,3] >>> lst[3] Traceback (most recent call last): File "<pyshell#45>", line 1, in <module> lst[3] IndexError: list index out of range 6
Λάθη κατά την εκτέλεση Πρέπει να διαβάσουμε και να καταλάβουμε το σφάλμα >>> y + 5 Traceback (most recent call last): File "<pyshell#46>", line 1, in <module> Και στα λάθη κατά την εκτέλεση y + 5 η γραμμή και το μήνυμα μας NameError: name 'y' is not defined δίνουν πληροφορίες για να >>> '2' * '3' καταλάβουμε το λάθος. Traceback (most recent call last): File "<pyshell#47>", line 1, in <module> '2' * '3' TypeError: can't multiply sequence by non-int of type 'str' 7
Λάθη κατά την εκτέλεση Πρέπει να διαβάσουμε και να καταλάβουμε το σφάλμα >>> int('4.5') Traceback (most recent call last): File "<pyshell#48>", line 1, in <module> int('4.5') ValueError: invalid literal for int() with base 10: '4.5' >>> 2.0**10000 Traceback (most recent call last): File "<pyshell#49>", line 1, in <module> 2.0**10000 OverflowError: (34, 'Result too large') 8
Εξαιρέσεις Όταν το πρόγραμμα μπει σε μια μη έγκυρη κατάσταση δημιουργεί μια εξαίρεση (exception) Η εξαίρεση είναι κι αυτή αντικείμενο Κάθε εξαίρεση ανήκει σε ένα τύπο Ο τύπος μας βοηθάει να καταλάβουμε το σφάλμα Εξαίρεση KeyboardInterrupt OverflowError Εξήγηση Ο χρήστης διέκοψε το πρόγραμμα με Crtl-C Ένας float πήρε πολύ μεγάλη τιμή ZeroDivisionError Διαίρεση με το 0 FileNotFoundError IndexError NameError TypeError ValueError Το όνομα αρχείου που επιχειρούμε να ανοίξουμε δεν υπάρχει Προσπαθήσαμε να προσπελάσουμε μια μη έγκυρη θέση ακολουθίας Προσπέλαση μεταβλητής που δεν έχει οριστεί Μια λειτουργία εφαρμόζεται σε λάθος τύπο αντικειμένου Μια λειτουργία εφαρμόζεται σε μη έγκυρη τιμή 9
Δημιουργία Εξαιρέσεων Μπορούμε να δημιουργήσουμε εξαίρεση στο πρόγραμμα ή τη συνάρτησή μας αν κάτι δεν είναι έγκυρο με βάση τις προδιαγραφές μας με την εντολή raise x = int(input('input a number between 10 and 20: ')) if x<10 or x>20: raise Exception('Number outside valid range') print(x**2) περιγραφή της εξαίρεσης Input a number between 10 and 20: 12 144 Input a number between 10 and 20: 3 Traceback (most recent call last): File "test.py", line 3, in <module> raise Exception('Number outside valid range') Exception: Number outside valid range 10
«Πιάσιμο» Εξαιρέσεων Μπορούμε να αποφύγουμε να τερματίσει το πρόγραμμά μας, φροντίζοντας να «πιάνουμε» τυχόν εξαιρέσεις try: x = eval(input('enter the first number: ')) y = eval(input('enter the second number: ')) print (x/y) except ZeroDivisionError: print("the second number can't be zero!") print("program finished normally...") Enter the first number: 3 Enter the second number: 4 0.75 program finished normally... 11
«Πιάσιμο» Εξαιρέσεων Μπορούμε να αποφύγουμε να τερματίσει το πρόγραμμά μας, φροντίζοντας να «πιάνουμε» τυχόν εξαιρέσεις try: x = eval(input('enter the first number: ')) y = eval(input('enter the second number: ')) print (x/y) except ZeroDivisionError: print("the second number can't be zero!") print("program finished normally...") Enter the first number: 3 Enter the second number: 0 The second number can't be zero! program finished normally... 12
Αλλά... try: x = eval(input('enter the first number: ')) y = eval(input('enter the second number: ')) print (x/y) except ZeroDivisionError: print("the second number can't be zero!") print("program finished normally...") Enter the first number: 3a Traceback (most recent call last): File "/Users/Nikos/Dropbox/courses/course_python/py/test.py", line 2, in <module> x = eval(input('enter the first number: ')) File "<string>", line 1 3a ^ SyntaxError: unexpected EOF while parsing 13
«Πιάσιμο» Εξαιρέσεων try: x = eval(input('enter the first number: ')) y = eval(input('enter the second number: ')) print (x/y) except ZeroDivisionError: print("the second number can't be zero!") except NameError: print("you did not give a number") except SyntaxError: print("you did not give a number ) οι εξαίρεσεις εξετάζονται με αυτή τη σειρά 14
«Πιάσιμο» Εξαιρέσεων try: x = eval(input('enter the first number: ')) y = eval(input('enter the second number: ')) print (x/y) except ZeroDivisionError: print("the second number can't be zero!") except: print("something wrong happened") αν δεν προσδιορίσουμε συγκεκριμένο τύπο εξαίρεσης μετά το except, τότε εννοούμε οποιαδήποτε εξαίρεση το σκέτο except πρέπει να μπαίνει με προσοχή γιατί πιάνει τα πάντα (ακόμα και το Ctrl-C) 15
«Πιάσιμο» Εξαιρέσεων try: x = eval(input('enter the first number: ')) y = eval(input('enter the second number: ')) print (x/y) except (ZeroDivisionError, NameError, SyntaxError): print("something wrong with your numbers") μπορούμε να βάλουμε πολλούς τύπους εξαιρέσεων 16
«Πιάσιμο» Εξαιρέσεων try: x = eval(input('enter the first number: ')) y = eval(input('enter the second number: ')) print (x/y) except (ZeroDivisionError, NameError, SyntaxError) as e: print(e) Enter the first number: 3a unexpected EOF while parsing (<string>, line 1) αποθηκεύει το αντικείμενο της εξαίρεσης στη μεταβλητή e 17
Χρήση try/except/else while True: try: x = eval(input('enter the first number: ')) y = eval(input('enter the second number: ')) value = x/y print ('x/y is', value) except Exception as e: print('invalid input: ',e) print('please try again.') else: break τυπώνουμε την εξαίρεση για να ξέρει ο χρήστης τι πήγε στραβά Το else αντιστοιχεί στην περίπτωση που δεν δημιουργήθηκε εξαίρεση. Τότε όλα πήγαν καλά και ο βρόγχος τερματίζει 18
Παράδειγμα Γράψτε μια συνάρτηση που θα μετατρέπει την πρώτη γραμμή ενός αρχείου σε ακέραιο και μετά θα τυπώνει τον ακέραιο def readage(filename): try: infile = open(filename) strage = infile.readline() age = int(strage) print('age is',age) except IOError: print('input/output error.') except ValueError: print('value cannot be converted to integer.') except: print('other error.') 19
Εξαιρέσεις και συναρτήσεις Αν δημιουργηθεί εξαίρεση (σφάλμα) μέσα σε μια συνάρτηση, τότε η συνάρτηση σταματάει και επιστέφει Μπορούμε να «πιάσουμε» εξαιρέσεις μέσα στη συνάρτηση και να τις χειριστούμε Επίσης μπορούμε να «πιάσουμε» μια εξαίρεση που δημιουργήθηκε σε μια συνάρτηση έξω από τη συνάρτηση Μια εξαίρεση που δημιουργείται σε μια συνάρτηση, αν δεν τη χειριστούμε εκεί, «μεταφέρεται» στο σημείο που καλείται η συνάρτηση, και με τον ίδιο τρόπο μέχρι το κυρίως πρόγραμμα Αν δεν χειριστούμε την εξαίρεση κάπου, το πρόγραμμα τερματίζει με σφάλμα δείχνοντας όλα τα σημεία από όπου πέρασε η εξαίρεση (στοίβα εκτέλεσης προγράμματος). 20
Εξαιρέσεις και συναρτήσεις def faulty(): raise Exception('Something is wrong') def ignore_exception(): faulty() def handle_exception(): try: faulty() except: print('exception handled') >>> ignore_exception() Traceback (most recent call last): File "<pyshell#12>", line 1, in <module> ignore_exception() File "test.py", line 5, in ignore_exception faulty() File "test.py", line 3, in faulty raise Exception('Something is wrong') Exception: Something is wrong 21
Εξαιρέσεις και συναρτήσεις def faulty(): raise Exception('Something is wrong') def ignore_exception(): faulty() def handle_exception(): try: faulty() except: print('exception handled') >>> handle_exception() Exception handled 22
Εξαιρέσεις και συναρτήσεις 23
Πιάνοντας εξαιρέσεις Είναι πολύ συνηθισμένο να πιάνουμε την εξαίρεση σε διαφορετικό σημείο από αυτό που δημιουργήθηκε. while True: try: x = int(input('give a number (not 2):')) f(x) except Exception as e: print(e) else: break Πιάνουμε την εξαίρεση όταν ξέρουμε τι θα την κάνουμε
Πετώντας και πιάνοντας εξαιρέσεις def read_digit(): x = int(input('give digit:')) if (x < 0 or x >9): raise Exception('More than a digit') return x while True: try: x = read_digit() except Exception as e: print(e) else: break print(x) Δημιουργούμε την εξαίρεση μέσα στην συνάρτηση και την πιάνουμε στο κύριο πρόγραμμα.
Παράδειγμα Έστω ότι αποθηκεύουμε τα στοιχεία προσώπων σαν λεξικά Αν ένα πρόσωπο δέν έχει κάποια στοιχεία, ή δεν τα γνωρίζουμε, απλά δεν υπάρχουν στο λεξικό του >>> john = {'age':34, 'name':'john'} >>> steve = {'age':23, 'occupation':'locksmith'} >>> john['age'] 34 >>> john['occupation'] Traceback (most recent call last): File "<pyshell#19>", line 1, in <module> person['occupation'] KeyError: 'occupation' 26
Παράδειγμα Θέλουμε να γράψουμε μια συνάρτηση describeperson() που να τυπώνει τα υπάρχοντα στοιχεία του προσώπου που της δίνεται σαν όρισμα Το name είναι υποχρεωτικό κλειδί στο όρισμα της συνάρτησης, τα υπόλοια στοιχεία, προαιρετικά >>> john = {'age':34, 'name':'john'} >>> steve = {'name':'steve', 'occupation':'locksmith'} >>> describeperson(john) Description of John Age: 34 >>> describeperson(steve) Description of Steve Occupation: locksmith 27
Παράδειγμα Θέλουμε να γράψουμε μια συνάρτηση describeperson() που να τυπώνει τα υπάρχοντα στοιχεία του προσώπου που της δίνεται σαν όρισμα def describeperson(person): print('description of', person['name']) if 'age' in person: print('age:', person['age']) if 'occupation' in person: print('occupation:', person['occupation']) if 'marital' in person: print('marital Status:', person['marital']) Βάζουμε ένα if για κάθε πιθανό στοιχείο! 28
Παράδειγμα Θέλουμε να γράψουμε μια συνάρτηση describeperson() που να τυπώνει τα υπάρχοντα στοιχεία του προσώπου που της δίνεται σαν όρισμα Με χρήση εξαιρέσεων: def describeperson(person): print('description of', person['name']) try: print('age:', person['age']) except KeyError: pass try: print('occupation:', person['occupation']) except KeyError: pass try: print('marital Status:', person['marital']) except KeyError: pass 29
Αποσφαλμάτωση Αν το πρόγραμμά μας δε λειτουργεί σωστά (π.χ. έχει λογικό σφάλμα) και δεν ξέρουμε που είναι το σφάλμα, πρέπει να χρησιμοποιήσουμε μια μέθοδο εκσφαλμάτωσης (debugging). Η εκσφαλμάτωση μπορεί να γίνει με ειδικά προγράμματα (debuggers) Ο debugger προσομοιώνει την εκτέλεση του προγράμματος εντολή-προς-εντολή και δίνει ανά πάσα στιγμή τις τιμές των μεταβλητών και τη στοίβα του προγράμματος Τα περισσότερα IDE (integrated development interfaces) εμπεριέχουν debugger 30
Αποσφαλμάτωση Τυπική μεθοδολογία εκσφαλμάτωσης: Επανάληψη του προγράμματος (ώστε να εντοπιστεί το σφάλμα) Απομόνωση του σημείου που εμφανίζεται το σφάλμα Αναγνώριση της αιτίας που το προκαλεί (π.χ. από απροσεξία ή από λογικό σφάλμα) Διόρθωση του σφάλματος Επιβεβαίωση της διόρθωσης (επανάληψη του προγράμματος, δοκιμή με διαφορετικές εισόδους) Τα παραπάνω βήματα είναι τυπικά. Κάποιοι προγραμματιστές μπορεί να ακολουθούν μια παραλλαγή της μεθοδολογίας αυτής 31
Συμβουλές για την πρόληψη σφαλμάτων Σχεδιάστε πρώτα τον αλγόριθμό σας στο χαρτί και βεβαιωθείτε ότι δεν έχει λογικά λάθη Γράφετε το πρόγραμμά σας λίγο-λίγο και εκτελείτε το δοκιμάζοντας την ορθότητά του (π.χ. με εντολές print) πριν προχωρήσετε στο να γράψετε τις επόμενες γραμμές του Δοκιμάζετε πάντα την ορθότητα με μια ποικιλία δεδομένων εισόδου και βεβαιωθείτε ότι είναι σωστό για όλες τις πιθανές εισόδους Κάντε χρήση συναρτήσεων ώστε να σπάσετε το πρόγραμμα σε μικρά υπο-προγράμματα τα οποία είναι ευκολότερο να ελεγχθούν Εισάγετε σχόλια ώστε να μπορέσετε να θυμηθείτε τη λειτουργία τμημάτων κώδικα που θα πρέπει να αλλάξετε σε περίπτωση σφάλματος 32