Σφάλματα και ένα μικρό παιχνίδι

Θέλεις τον μέσο λογαριασμό ρεύματος για τους τρεις μήνες του χειμώνα. Οι λογαριασμοί ήταν 60, 75 και 90 ευρώ. Γράφεις το πρόγραμμα, το τρέχεις και δε βγαίνει κανένα μήνυμα σφάλματος:
python
Τυπώνει Μέσος όρος: 165.0. Ο μέσος όρος τριών λογαριασμών δεν μπορεί να είναι μεγαλύτερος από τον ακριβότερο, οπότε κάτι είναι λάθος, αλλά η Python δε σου λέει τι. Από την άποψή της όλα πήγαν καλά: έκανε ακριβώς τις πράξεις που έγραψες.
Πριν διορθώσεις αυτό το πρόγραμμα, ξεχώρισε τα είδη των λαθών, γιατί το καθένα το ψάχνεις αλλιώς.

Τρία είδη λαθών

Το συντακτικό λάθος σημαίνει ότι ο κώδικας δεν είναι γραμμένος όπως τον δέχεται η Python. Μια : που λείπει, μια παρένθεση που δεν έκλεισε, τα ελληνικά εισαγωγικά «». Η Python διαβάζει όλο το πρόγραμμα πριν τρέξει οτιδήποτε, οπότε δεν τρέχει ούτε η πρώτη γραμμή:
text
File "main.py", line 2
    if grade >= 10
                  ^
SyntaxError: expected ':'
Το σφάλμα κατά την εκτέλεση εμφανίζεται σε κώδικα σωστά γραμμένο, την ώρα που τρέχει. Το πρόγραμμα κάνει κανονικά όσες γραμμές προλάβει και σταματάει σε εκείνη όπου κάτι δεν μπορεί να γίνει. Το όνομα του σφάλματος λέει τι δεν μπόρεσε να γίνει:
ΣφάλμαΤι συνέβη
NameErrorΧρησιμοποίησες ένα όνομα που δεν υπάρχει, από τυπογραφικό λάθος ή πριν το φτιάξεις
TypeErrorΗ πράξη πήρε τιμή λάθος τύπου, όπως κείμενο συν αριθμό
ValueErrorΟ τύπος ήταν σωστός, το περιεχόμενο όχι, όπως int("τέσσερα")
IndexErrorΖήτησες θέση λίστας που δεν υπάρχει
Το λογικό λάθος είναι το πρόγραμμα του ρεύματος: τρέχει μέχρι το τέλος, δε βγάζει κανένα μήνυμα και δίνει λάθος αποτέλεσμα. Είναι το πιο δύσκολο, γιατί τίποτα δε σου δείχνει ότι υπάρχει. Το καταλαβαίνεις μόνο αν ξέρεις περίπου τι αποτέλεσμα περιμένεις. Αυτό ισχύει και για κώδικα που σου έγραψε ένα εργαλείο AI: μπορεί να τρέχει χωρίς μήνυμα και να κρύβει ακριβώς τέτοιο λάθος.
Γιατί ένα πρόγραμμα δε δουλεύει Διάγραμμα ψαροκόκαλου με κεφάλι «Το πρόγραμμα δε δουλεύει» και τρία κόκαλα. Συντακτικό λάθος: δεν ξεκινάει καθόλου, για παράδειγμα λείπει η άνω και κάτω τελεία ή μένει ανοιχτή μια παρένθεση. Σφάλμα εκτέλεσης: σταματάει σε μια γραμμή, με NameError, TypeError, ValueError ή IndexError. Λογικό λάθος: τρέχει, αλλά βγάζει λάθος αποτέλεσμα, για παράδειγμα από λάθος σειρά πράξεων ή από range που πάει μία θέση πιο πέρα. Το πρόγραμμα δε δουλεύει Συντακτικό λάθος δεν ξεκινάει καθόλου λείπει η : ανοιχτή παρένθεση Λογικό λάθος τρέχει, αλλά λάθος αποτέλεσμα σειρά πράξεων range κατά ένα Σφάλμα εκτέλεσης σταματάει σε μια γραμμή NameError, TypeError ValueError, IndexError
Ένα e-shop αθροίζει τις τιμές του καλαθιού:
Πρόβλεψε
prices = [12.9, 4.5, 30.0]
total = 0
for i in range(1, len(prices)):
    total += prices[i]
print(total)

Τι θα εμφανιστεί στην οθόνη;

Διάλεξε μία

Πώς ψάχνεις ένα λάθος

Το ψάξιμο και η διόρθωση ενός λάθους λέγεται debugging. Για αρχή σου αρκεί μια απλή μέθοδος:
  1. Αν υπάρχει μήνυμα σφάλματος, διάβασε πρώτα την τελευταία γραμμή του. Λέει τι έγινε. Οι γραμμές από πάνω λένε πού.
  2. Αν δεν υπάρχει μήνυμα, τύπωσε τις ενδιάμεσες τιμές. Ένα print πριν από το αποτέλεσμα σου δείχνει σε ποιο βήμα η τιμή σταμάτησε να είναι αυτή που περίμενες.
  3. Άλλαζε ένα πράγμα τη φορά και ξανάτρεχε. Αν αλλάξεις τρία μαζί και το πρόγραμμα δουλέψει, δεν ξέρεις ποιο από τα τρία το διόρθωσε.
Πήγαινε πάλι στο πρόγραμμα του ρεύματος και εφάρμοσε το δεύτερο βήμα. Πρόσθεσε πριν από το print δύο γραμμές, print(january + february + march) και print(march / 3), και τρέξε ξανά. Θα δεις 225 και 30.0: το άθροισμα είναι σωστό, αλλά η διαίρεση έγινε μόνο στο 90. Η Python κάνει τη διαίρεση πριν από την πρόσθεση, όπως στα μαθηματικά. Βάλε το άθροισμα σε παρένθεση, σβήσε τα βοηθητικά print και ο μέσος όρος πρέπει να βγει 75.0.
Το μήνυμα ενός σφάλματος εκτέλεσης λέγεται traceback και όταν το σφάλμα γίνεται σε μια συνάρτηση, έχει περισσότερες γραμμές. Τρέξε αυτό:
python
Η πρώτη κλήση τυπώνει 15.5. Η δεύτερη κρασάρει:
text
Traceback (most recent call last):
  File "main.py", line 8, in <module>
    print(average([]))
          ~~~~~~~^^^^
  File "main.py", line 5, in average
    return total / len(grades)
           ~~~~~~^~~~~~~~~~~~~
ZeroDivisionError: division by zero
Νέο όνομα σφάλματος: ZeroDivisionError σημαίνει διαίρεση με το μηδέν. Το traceback το διαβάζεις από κάτω προς τα πάνω. Η τελευταία γραμμή λέει τι έγινε. Από πάνω, line 5, in average, είναι η γραμμή της συνάρτησης όπου σταμάτησε. Ακόμα πιο πάνω, line 8, in <module>, είναι η γραμμή του κύριου προγράμματος που την κάλεσε. Το <module> είναι ο τρόπος της Python να πει «το κύριο πρόγραμμα». Η λίστα ήταν άδεια, το len(grades) έδωσε 0 και η διαίρεση δεν μπορούσε να γίνει.
Διόρθωσέ το: βάλε στην αρχή του σώματος της average ένα if len(grades) == 0: που κάνει return 0. Το return τελειώνει τη συνάρτηση αμέσως, οπότε το loop και η διαίρεση δεν τρέχουν για την άδεια λίστα. Τώρα πρέπει να δεις 15.5 και 0. (Το 0 είναι απλώς μια επιλογή για την άδεια λίστα.)

Ένα μικρό παιχνίδι

Ένας φίλος σκέφτεται έναν αριθμό από το 1 ως το 100 και εσύ προσπαθείς να τον βρεις. Σε κάθε μαντεψιά σου λέει «Μεγαλύτερο» ή «Μικρότερο», ώσπου να τον βρεις. Εδώ ο κρυφός αριθμός και οι μαντεψιές σου είναι γραμμένα στην κορυφή του προγράμματος και τη σύγκριση την κάνει μια συνάρτηση:
python
Τρέξε το. Ο φίλος σου είπε «Βρήκες!», αλλά το πρόγραμμα συνεχίζει και ρωτάει και για το 40. Χρειάζεσαι έναν τρόπο να βγεις από το loop μόλις βρεθεί ο αριθμός. Αυτό κάνει το break: σταματάει το loop αμέσως, ακόμα κι αν μένουν στοιχεία στη λίστα, και η Python συνεχίζει στην πρώτη γραμμή μετά το loop.
Για να αποφασίσει αν θα κάνει break, το πρόγραμμα πρέπει να ελέγξει το μήνυμα, γι' αυτό η hint το επιστρέφει και δεν το τυπώνει. Μετά το loop θέλεις να ξέρεις αν βγήκες με break ή επειδή τελείωσαν οι μαντεψιές. Για αυτό κρατάς το found. Προχώρα βήμα βήμα με τρεις μαντεψιές. Κάθε φορά που η γραμμή 5 καλεί την hint, το βήμα δείχνει κατευθείαν τι επέστρεψε:
Βήμα προς βήμα
Βήμα 1 από 11
secret = 37
guesses = [50, 37, 40]
found = False
for guess in guesses:
message = hint(guess, secret)
print(guess, message)
if message == "Βρήκες!":
found = True
break
if not found:
print("Δεν το βρήκες. Ήταν το", secret)
Μεταβλητές

secret = 37

guesses = [50, 37, 40]

found = False

Ο κρυφός αριθμός, οι μαντεψιές και το found, που θα γίνει True μόνο αν βρεις τον αριθμό.

Η λογική του παιχνιδιού Διάγραμμα ροής του παιχνιδιού. Με το secret και τη λίστα guesses, το πρόγραμμα ρωτάει αν μένει μαντεψιά. Αν όχι, τυπώνει Δεν το βρήκες. Αν ναι, ρωτάει αν guess < secret και τυπώνει Μεγαλύτερο, αλλιώς ρωτάει αν guess > secret και τυπώνει Μικρότερο. Και στις δύο περιπτώσεις πηγαίνει στην επόμενη μαντεψιά. Αν δεν ισχύει καμία από τις δύο, τυπώνει Βρήκες! και το break σταματάει το loop. Ναι Όχι True False True False ξανά secret, guesses Μένει μαντεψιά; Δεν το βρήκες guess < secret Μεγαλύτερο guess > secret Μικρότερο Βρήκες! και break
Εδώ είναι ολόκληρο το παιχνίδι. Άλλαξε τις μαντεψιές στη δεύτερη γραμμή και τρέξε ξανά. Δοκίμασε μια λίστα που δεν περιέχει το 37, για να δεις το τελευταίο μήνυμα. Μετά ζήτα από κάποιον να αλλάξει το secret χωρίς να σου πει ποιο είναι και προσπάθησε να το βρεις με όσο λιγότερες μαντεψιές μπορείς.
python

Το ίδιο σε ψευδοκώδικα

Το παιχνίδι σε ψευδοκώδικα, με τη σύγκριση γραμμένη κατευθείαν στο loop αντί για συνάρτηση:
ΓΛΩΣΣΑ
ΠΡΟΓΡΑΜΜΑ ΜΑΝΤΕΨΙΑ
ΜΕΤΑΒΛΗΤΕΣ
  ΑΚΕΡΑΙΕΣ: ΚΡΥΦΟΣ, ΜΑΝΤΕΨΙΕΣ[4], i
  ΛΟΓΙΚΕΣ: ΒΡΕΘΗΚΕ
ΑΡΧΗ
  ΚΡΥΦΟΣ ← 37
  ΜΑΝΤΕΨΙΕΣ[1] ← 50
  ΜΑΝΤΕΨΙΕΣ[2] ← 25
  ΜΑΝΤΕΨΙΕΣ[3] ← 37
  ΜΑΝΤΕΨΙΕΣ[4] ← 40
  ΒΡΕΘΗΚΕ ← ΨΕΥΔΗΣ
  i ← 1
  ΟΣΟ i <= 4 ΚΑΙ ΒΡΕΘΗΚΕ = ΨΕΥΔΗΣ ΕΠΑΝΑΛΑΒΕ
    ΑΝ ΜΑΝΤΕΨΙΕΣ[i] < ΚΡΥΦΟΣ ΤΟΤΕ
      ΓΡΑΨΕ ΜΑΝΤΕΨΙΕΣ[i], 'Μεγαλύτερο'
    ΑΛΛΙΩΣ_ΑΝ ΜΑΝΤΕΨΙΕΣ[i] > ΚΡΥΦΟΣ ΤΟΤΕ
      ΓΡΑΨΕ ΜΑΝΤΕΨΙΕΣ[i], 'Μικρότερο'
    ΑΛΛΙΩΣ
      ΓΡΑΨΕ ΜΑΝΤΕΨΙΕΣ[i], 'Βρήκες!'
      ΒΡΕΘΗΚΕ ← ΑΛΗΘΗΣ
    ΤΕΛΟΣ_ΑΝ
    i ← i + 1
  ΤΕΛΟΣ_ΕΠΑΝΑΛΗΨΗΣ
  ΑΝ ΒΡΕΘΗΚΕ = ΨΕΥΔΗΣ ΤΟΤΕ
    ΓΡΑΨΕ 'Δεν το βρήκες. Ήταν το', ΚΡΥΦΟΣ
  ΤΕΛΟΣ_ΑΝ
ΤΕΛΟΣ_ΠΡΟΓΡΑΜΜΑΤΟΣ
Η ΓΛΩΣΣΑ δεν έχει break. Τη δουλειά του την κάνει η συνθήκη του ΟΣΟ: το loop συνεχίζει όσο υπάρχουν μαντεψιές και δεν έχεις βρει τον αριθμό. Το ΒΡΕΘΗΚΕ είναι το found της Python.

Σύνοψη

  • Το συντακτικό λάθος (SyntaxError) δεν αφήνει το πρόγραμμα να ξεκινήσει. Το σφάλμα κατά την εκτέλεση το σταματάει στη γραμμή του προβλήματος. Το λογικό λάθος δε σταματάει τίποτα και δίνει λάθος αποτέλεσμα.
  • Το λογικό λάθος το βρίσκεις μόνο αν ξέρεις περίπου τι αποτέλεσμα περιμένεις.
  • Στο traceback διαβάζεις πρώτα την τελευταία γραμμή, που λέει τι έγινε. Οι γραμμές από πάνω δείχνουν πού, από τη συνάρτηση προς τη γραμμή που την κάλεσε.
  • Όταν δεν υπάρχει μήνυμα, τυπώνεις ενδιάμεσες τιμές και αλλάζεις ένα πράγμα τη φορά.
  • Το return τελειώνει μια συνάρτηση αμέσως. Το break τελειώνει ένα loop αμέσως.
  • Το break σταματάει το loop μόλις βρεις αυτό που ψάχνεις. Ένα found σου λέει μετά το loop αν το βρήκες.

Θες να συνεχίσεις;

Το course είναι δωρεάν. Κάνε σύνδεση για να κρατάς την πρόοδο και τις σημειώσεις σου.