Πώς να διαβάζεις tracebacks

Μια επισκόπηση του πώς να διαβάζεις tracebacks της Python για αποσφαλμάτωση.


Αντικείμενο frame

Όταν καλείται μια συνάρτηση, δημιουργείται ένα αντικείμενο frame για να κρατήσει τις τοπικές μεταβλητές και τα ορίσματα που περνούν στη συνάρτηση. Όταν η συνάρτηση επιστρέφει, το αντικείμενο frame καταστρέφεται. Όταν η συνάρτηση B καλείται μέσα στη συνάρτηση A, οι τιμές της συνάρτησης B τοποθετούνται σε ένα αντικείμενο frame, το οποίο μετά μπαίνει πάνω από το αντικείμενο frame της συνάρτησης A στη στοίβα κλήσεων.

Στοίβα κλήσεων

Η στοίβα κλήσεων είναι μια συλλογή από αντικείμενα frame για τις συναρτήσεις που είναι ενεργές εκείνη τη στιγμή. Αν η συνάρτηση A έχει καλέσει τη συνάρτηση B και η συνάρτηση B έχει καλέσει τη συνάρτηση C, τότε τα αντικείμενα frame και των τριών συναρτήσεων θα βρίσκονται στη στοίβα κλήσεων. Μόλις επιστρέψει η συνάρτηση C, το αντικείμενο frame της θα αφαιρεθεί από τη στοίβα και μόνο τα αντικείμενα frame των συναρτήσεων A και B θα παραμείνουν στη στοίβα κλήσεων.

Traceback

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

Πώς να διαβάζεις ένα traceback

Το ValueError είναι μια συνηθισμένη εξαίρεση.

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

>>> first, second = [1]
Traceback (most recent call last):
File <stdin>, line 1, in <module>
    first, second = [1]
ValueError: not enough values to unpack (expected 2, got 1)

Τα tracebacks οργανώνονται με την πιο πρόσφατη κλήση στο τέλος, οπότε το σημείο από όπου ξεκινάς να διαβάζεις το traceback είναι η εξαίρεση στο κάτω μέρος. Διαβάζοντας προς τα πάνω από εκεί, βλέπεις πώς η εκτέλεση έφτασε σε αυτή την εντολή. Αν βάλεις την προβληματική γραμμή μέσα σε μια συνάρτηση και μετά καλέσεις τη συνάρτηση, θα δεις ένα μεγαλύτερο trace:

>>> def my_func():
...     first, second = [1]
...
>>> my_func()
Traceback (most recent call last):
  File <stdin>, line 5, in <module>
    my_func()
  File <stdin>, line 2, in my_func
    first, second = [1]
ValueError: not enough values to unpack (expected 2, got 1)

Κοιτώντας ανάποδα από το κάτω μέρος, βλέπεις ότι η κλήση όπου συνέβη η εξαίρεση βρίσκεται στη γραμμή 2 μέσα στη my_func. Έφτασες εκεί καλώντας τη my_func στη γραμμή 5.

Συνηθισμένες εξαιρέσεις

Η Python ορίζει πάνω από 60 ενσωματωμένες κλάσεις εξαιρέσεων. Ακολουθεί μια σύντομη επισκόπηση μερικών από τις πιο συνηθισμένες εξαιρέσεις και το τι σημαίνουν.

SyntaxError

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

Πάτησε εδώ για ένα παράδειγμα κώδικα.

Τρέχοντας αυτόν τον κώδικα:

def distance(strand_a, strand_b):
    if len(strand_a) != len(strand_b):
        raise ValueError("Strands must be of equal length."  # This is missing the closing parenthesis

θα έχει ως αποτέλεσμα ένα stack trace παρόμοιο με αυτό (πρόσεξε το μήνυμα στην τελευταία γραμμή):

.usr.local.lib.python3.10.site-packages._pytest.python.py:608: in _importtestmodule
    mod = import_path(self.path, mode=importmode, root=self.config.rootpath)
.usr.local.lib.python3.10.site-packages._pytest.pathlib.py:533: in import_path
    importlib.import_module(module_name)
.usr.local.lib.python3.10.importlib.__init__.py:126: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
<frozen importlib._bootstrap>:1050: in _gcd_import ???
<frozen importlib._bootstrap>:1027: in _find_and_load ???
<frozen importlib._bootstrap>:1006: in _find_and_load_unlocked ???
<frozen importlib._bootstrap>:688: in _load_unlocked ???
.usr.local.lib.python3.10.site-packages._pytest.assertion.rewrite.py:168: in exec_module
    exec(co, module.__dict__)
.mnt.exercism-iteration.hamming_test.py:3: in <module>
    from hamming import (  
E     File ".mnt.exercism-iteration.hamming.py", line 10
E       raise ValueError("Strands must be of equal length."
E                       ^
E   SyntaxError: '(' was never closed

AssertionError

Η Python πετάει ένα AssertionError όταν αποτύχει μια εντολή assert (δες παρακάτω).

Πάτησε εδώ για ένα παράδειγμα κώδικα.

Τρέχοντας αυτόν τον κώδικα:

def distance(strand_a, strand_b):
    assert len(strand_a) == len(strand_b)


distance("ab", "abc")

θα έχει ως αποτέλεσμα ένα stack trace παρόμοιο με αυτό (πρόσεξε το μήνυμα στην τελευταία γραμμή):

hamming_test.py:3: in <module>
    from hamming import (
hamming.py:5: in <module>
    distance("ab", "abc")
hamming.py:2: in distance
    assert len(strand_a) == len(strand_b)
E   AssertionError

AttributeError

Ένα AttributeError πετιέται όταν ο κώδικας (ή ένα unit test!) προσπαθεί να προσπελάσει ένα χαρακτηριστικό ενός αντικειμένου, αλλά το αντικείμενο δεν έχει τέτοιο χαρακτηριστικό. Για παράδειγμα, ένα unit test περιμένει ένα αντικείμενο Robot να έχει ένα χαρακτηριστικό direction, αλλά όταν προσπάθησε να προσπελάσει το robot.direction, αυτό δεν υπάρχει.

Αυτό μπορεί επίσης να υποδηλώνει ένα τυπογραφικό λάθος, όπως το να χρησιμοποιήσεις "Hello".lowercase() όταν η σωστή σύνταξη είναι "Hello".lower(). Το "Hello".lowercase() πετάει AttributeError: 'str' object has no attribute 'lowercase'.

Πάτησε εδώ για ένα παράδειγμα με κλάση Robot.

Τρέχοντας αυτόν τον κώδικα:

class Robot:
    def __init__():
        #note that there is no self.direction listed here
        self.position = (0, 0)
        self.orientation = 'SW'

    def forward():
        pass
        

robby = Robot
robby.direction

θα έχει ως αποτέλεσμα ένα stack trace παρόμοιο με αυτό (πρόσεξε το μήνυμα στην τελευταία γραμμή):

robot_simulator_test.py:3: in <module>
    from robot_simulator import (
robot_simulator.py:12: in <module>
    robby.direction
E   AttributeError: type object 'Robot' has no attribute 'direction'
Πάτησε εδώ για ένα παράδειγμα με συμβολοσειρά.

Τρέχοντας αυτόν τον κώδικα:

def distance(strand_a, strand_b):
    if strand_a.lowercase() == strand_b:
        return 0


distance("ab", "abc")

θα έχει ως αποτέλεσμα ένα stack trace παρόμοιο με αυτό (πρόσεξε το μήνυμα στην τελευταία γραμμή):

    def distance(strand_a, strand_b):
>       if strand_a.lowercase() == strand_b:
E       AttributeError: 'str' object has no attribute 'lowercase'

ImportError

Ένα ImportError πετιέται όταν ο κώδικας προσπαθεί να εισάγει κάτι, αλλά η Python δεν μπορεί να το κάνει. Για παράδειγμα, ένα unit test για το Guidos Gorgeous Lasagna κάνει from lasagna import bake_time_remaining, αλλά το αρχείο λύσης lasgana.py μπορεί να μην ορίζει το bake_time_remaining.

Πάτησε εδώ για παράδειγμα κώδικα

Αν τρέξεις το αρχείο lasgana.py χωρίς να έχει οριστεί η συνάρτηση, θα πάρεις το παρακάτω σφάλμα:

We received the following error when we ran your code:

  ImportError while importing test module '.mnt.exercism-iteration.lasagna_test.py'.
Hint: make sure your test modules.packages have valid Python names.

Traceback:
.mnt.exercism-iteration.lasagna_test.py:6: in <module>
    from lasagna import (EXPECTED_BAKE_TIME,
E   ImportError: cannot import name 'bake_time_remaining' from 'lasagna' (.mnt.exercism-iteration.lasagna.py)

During handling of the above exception, another exception occurred:
.usr.local.lib.python3.10.importlib.__init__.py:126: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
.mnt.exercism-iteration.lasagna_test.py:23: in <module>
    raise ImportError("In your 'lasagna.py' file, we can not find or import the"

E   ImportError: In your 'lasagna.py' file, we can not find or import the function named 'bake_time_remaining()'. Did you mis-name or forget to define it?

### **IndexError**

Python raises an `IndexError` when an invalid index is used to look up a value in a sequence.
This often indicates the index is not computed properly and is often an off-by-one error.


<details>
<summary>Click here for code example</summary>


Consider the following code.

```python
def distance(strand_a, strand_b):
    same = 0
    for i in range(len(strand_a)):
        if strand_a[i] == strand_b[i]:
            same += 1
    return same


distance("abc", "ab")  # Note the first strand is longer than the second strand.

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

hamming_test.py:3: in <module>
    from hamming import (
hamming.py:9: in <module>
    distance("abc", "ab")  # Note the first strand is longer than the second strand.
hamming.py:4: in distance
    if strand_a[i] == strand_b[i]:
E   IndexError: string index out of range

KeyError

Όπως και με το IndexError, αυτή η εξαίρεση πετιέται όταν χρησιμοποιείς ένα κλειδί για να ανακτήσεις μια τιμή από ένα λεξικό, αλλά το κλειδί δεν υπάρχει στο λεξικό.

Πάτησε εδώ για παράδειγμα κώδικα

Δες τον παρακάτω κώδικα.

def to_rna(dna_letter):
    translation = {"G": "C", "C": "G", "A": "U", "T": "A"}
    return translation[dna_letter]


print(to_rna("Q"))  # Note, "Q" is not in the translation.

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

rna_transcription_test.py:3: in <module>
    from rna_transcription import to_rna
rna_transcription.py:6: in <module>
    print(to_rna("Q"))
rna_transcription.py:3: in to_rna
    return translation[dna_letter]
E   KeyError: 'Q'

TypeError

Συνήθως, ένα TypeError πετιέται όταν ο λάθος τύπος δεδομένων περνιέται σε μια συνάρτηση ή χρησιμοποιείται σε μια πράξη.

Πάτησε εδώ για παράδειγμα κώδικα

Δες τον παρακάτω κώδικα.

def hello(name):  # This function expects a string.
    return 'Hello, ' + name + '!'


print(hello(100))  # 100 is not a string.

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

hello_world_test.py:3: in <module>
    import hello_world
hello_world.py:5: in <module>
    print(hello(100))
hello_world.py:2: in hello
    return 'Hello, ' + name + '!'
E   TypeError: can only concatenate str (not "int") to str

ValueError

Ένα ValueError πετιέται συνήθως όταν μια μη έγκυρη τιμή περνιέται σε μια συνάρτηση.

Πάτησε εδώ για παράδειγμα κώδικα

Πρόσεξε, πραγματικές τετραγωνικές ρίζες υπάρχουν μόνο για θετικούς αριθμούς. Η κλήση math.sqrt(-1) θα πετάξει ValueError: math domain error, αφού το -1 δεν είναι έγκυρη τιμή για μια τετραγωνική ρίζα. Με (μαθηματικούς) τεχνικούς όρους, το -1 δεν ανήκει στο πεδίο ορισμού των τετραγωνικών ριζών.

import math

math.sqrt(-1)

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

square_root_test.py:3: in <module>
    from square_root import (
square_root.py:3: in <module>
    math.sqrt(-1)
E   ValueError: math domain error

Χρήση της συνάρτησης print

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

# the intent is to pass an integer to this function and get an integer back
def halve_and_quadruple(num):
    return (num / 2) * 4

Όταν στη συνάρτηση περαστεί το 5, η αναμενόμενη τιμή είναι 8, αλλά επιστρέφει 10.0. Για να εντοπίσεις το πρόβλημα, ο υπολογισμός σπάει σε βήματα, ώστε να μπορείς να ελέγξεις την τιμή σε κάθε βήμα.

# the intent is to pass an integer to this function and get an integer back
def halve_and_quadruple(num):
    # verify the number in is what is expected
    # prints 5
    print(num)
    # we want the int divided by an integer to be an integer
    # but this prints 2.5! We've found our mistake.
    print(num / 2)
    # this makes sense, since 2.5 x 4 = 10.0
    print((num / 2) * 4)
    return (num / 2) * 4

What the `print` calls revealed is that we used `/` when we should have used `//`, the [floor division operator][floor division operator].

## Logging

[Logging][logging] can be used similarly to `print`, but it is more powerful.
What is logged can be configured by the logging severity (e.g., 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'.)
A call to the `logging.error` function can pass `True` to the `exc_info` parameter, which will additionally log the stack trace.
By configuring multiple handlers, logging can write to more than one place with the same logging function.

Following is an example of logging printed to the console:

```python

>>> import logging
>>>
>>> # configures minimum logging level as INFO
>>> logging.basicConfig(level=logging.INFO)
>>>
>>> def halve_and_quadruple(num):
...     # prints INFO:root: num == 5
...     logging.info(f" num == {num}")
...     return (num // 2) * 4
...
>>> print(halve_and_quadruple(5))

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

>>> import logging
...
>>> # configures the output file name to example.log, and the minimum logging level as INFO
>>> logging.basicConfig(filename='example.log', level=logging.INFO)
...
... def halve_and_quadruple(num):
...     # prints INFO:root: num == 5 to the example.log file
...     logging.info(f" num == {num}")
...     return (num // 2) * 4
...
>>> print(halve_and_quadruple(5))

assert

assert είναι μια εντολή που θα πρέπει πάντα να αξιολογείται ως True, εκτός αν υπάρχει κάποιο bug στο πρόγραμμα. Όταν ένα assert αξιολογείται ως False, θα πετάξει ένα AssertionError. Το traceback για το AssertionError μπορεί να περιλαμβάνει ένα προαιρετικό μήνυμα που αποτελεί μέρος της εντολής assert. Αν και το μήνυμα είναι προαιρετικό, είναι καλή πρακτική να περιλαμβάνεις πάντα ένα στον ορισμό του assert.

Το παρακάτω είναι ένα παράδειγμα χρήσης του assert:

>>> def int_division(dividend, divisor):
...     assert divisor != 0, "divisor must not be 0"
...     return dividend // divisor
...
>>> print(int_division(2, 1))
2
>>> print(int_division(2, 0))
Traceback (most recent call last):
  File <stdin>, line 7, in <module>
    print(int_division(2, 0))
          ^^^^^^^^^^^^^^^^^^
  File <stdin>, line 2, in int_division
    assert divisor != 0, "divisor must not be 0"
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: divisor must not be 0

Αν αρχίσεις να διαβάζεις το traceback από το κάτω μέρος (όπως πρέπει), βλέπεις γρήγορα ότι το πρόβλημα είναι ότι το 0 δεν θα έπρεπε να περνιέται ως divisor.

Το assert μπορεί επίσης να χρησιμοποιηθεί για να ελέγξεις ότι μια τιμή είναι του αναμενόμενου τύπου:

>>> import numbers
...
...
... def int_division(dividend, divisor):
...     assert divisor != 0, "divisor must not be 0"
...     assert isinstance(divisor, numbers.Number), "divisor must be a number"
...     return dividend // divisor
...
>>> print(int_division(2, 1))
2
>>> print(int_division(2, '0'))
Traceback (most recent call last):
  File <stdin>, line 11, in <module>
    print(int_division(2, '0'))
          ^^^^^^^^^^^^^^^^^^^^
  File <stdin>, line 6, in int_division
    assert isinstance(divisor, numbers.Number), "divisor must be a number"
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: divisor must be a number

Μόλις εντοπιστεί ένα bug, σκέψου να αντικαταστήσεις το assert με χειρισμό σφαλμάτων. Αυτό συμβαίνει επειδή όλες οι εντολές assert μπορούν να απενεργοποιηθούν τρέχοντας την Python με τις επιλογές -O ή -OO, ή ορίζοντας τη μεταβλητή περιβάλλοντος PYTHONOPTIMIZE σε 1 ή 2. Ο ορισμός του PYTHONOPTIMIZE σε 1 ισοδυναμεί με την εκτέλεση της Python με την επιλογή -O, που απενεργοποιεί τις εντολές assert. Ο ορισμός του PYTHONOPTIMIZE σε 2 ισοδυναμεί με την εκτέλεση της Python με την επιλογή -OO, που και απενεργοποιεί τις εντολές assert και αφαιρεί τα docstrings από τον bytecode. Η μείωση του bytecode είναι ένας τρόπος να τρέξει ο κώδικας πιο γρήγορα.

Ο debugger της Python

Η Python έχει έναν ενσωματωμένο debugger, το pdb. Μπορείς να τον χρησιμοποιήσεις για να εκτελέσεις τον κώδικα βήμα βήμα και να εξετάσεις τις μεταβλητές. Μπορείς επίσης να ορίσεις breakpoints με αυτόν. Για να ξεκινήσεις, πρέπει πρώτα να κάνεις import pdb και μετά να καλέσεις το pdb.set_trace() εκεί όπου θέλεις να ξεκινήσεις το debugging:

import pdb

def add(num1, num2):
    return num1 + num2

pdb.set_trace()
sum = add(1,5)
print(sum)

Τρέχοντας αυτόν τον κώδικα θα σου εμφανιστεί ένα prompt του pdb, όπου μπορείς να πληκτρολογήσεις εντολές. Γράψε help για να πάρεις μια λίστα με τις εντολές. Οι πιο συνηθισμένες είναι η step, που μπαίνει μέσα σε μια συνάρτηση που καλείται σε εκείνη τη γραμμή. Η next προσπερνά μια κλήση συνάρτησης και πηγαίνει στην επόμενη γραμμή. Η where σου λέει σε ποια γραμμή βρίσκεσαι. Μερικές άλλες χρήσιμες εντολές είναι η whatis <variable>, που σου λέει τον τύπο μιας μεταβλητής, και η print(<variable>), που τυπώνει την τιμή μιας μεταβλητής. Μπορείς επίσης να χρησιμοποιήσεις απλώς το <variable> για να τυπώσεις την τιμή μιας μεταβλητής. Μια άλλη εντολή είναι η jump <line number>, που πηγαίνει σε έναν συγκεκριμένο αριθμό γραμμής.

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

>>> python pdb.py
... > c:\pdb.py(7)<module>()
... -> sum = add(1,5)
... (Pdb)
>>> step
... > c:\pdb.py(3)add()
... -> def add(num1, num2):
... (Pdb)
>>> whatis num1
... <class 'int'>
>>> print(num2)
... 5
>>> next
... > c:\pdb.py(4)add()
... -> return num1 + num2
... (Pdb)
>>> jump 3
... > c:\pdb.py(3)add()
... -> def add(num1, num2):
... (Pdb)

Τα breakpoints ορίζονται με το break <filename>:<line number> <condition>, όπου η συνθήκη είναι προαιρετική και πρέπει να είναι αληθής για να ενεργοποιηθεί το breakpoint. Μπορείς απλώς να γράψεις break για να πάρεις μια λίστα με τα breakpoints που έχεις ορίσει. Για να απενεργοποιήσεις ένα breakpoint, μπορείς να γράψεις disable <breakpoint number>. Για να ενεργοποιήσεις ένα breakpoint, μπορείς να γράψεις enable <breakpoint number>. Για να διαγράψεις ένα breakpoint, μπορείς να γράψεις clear <breakpoint number>. Για να συνεχίσεις την εκτέλεση, μπορείς να γράψεις continue ή c. Για να βγεις από τον debugger, μπορείς να γράψεις quit ή q.

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

>>> python pdb.py
... > c:\pdb.py(7)<module>()
... -> sum = add(1,5)
... (Pdb)
>>> break
...
>>> break pdb:4
... Breakpoint 1 at c:\pdb.py:4
>>> break
... Num Type         Disp Enb   Where
... 2   breakpoint   keep yes   at c:\pdb.py:4
>>> c # continue
... > c:\pdn.py(4)add()
... -> return num1 + num2
>>> disable break 1
... Disabled breakpoint 1 at c:\pdb.py:4
>>> break
... Num Type         Disp Enb   Where
... 1   breakpoint   keep no    at c:\pdb.py:4
...         breakpoint already hit 1 time
>>> clear break 1
... Deleted breakpoint 1 at c:\pdb.py:4
>>> break
...

Στην Python 3.7+ υπάρχει ένας πιο εύκολος τρόπος να δημιουργήσεις breakpoints. Το να γράψεις απλώς breakpoint() εκεί όπου το χρειάζεσαι θα δημιουργήσει ένα.

def add(num1, num2):
    breakpoint()
    return num1 + num2

breakpoint()
sum = add(1,5)
print(sum)
>>> python pdb.py
... > c:\pdb.py(7)<module>()
... -> sum = add(1,5)
... (Pdb)
>>> c # continue
... > c:\pdb.py(5)add()
... -> return num1 + num2