Debugging

Tips for navigating the slides:
  • Press O or Escape for overview mode.
  • Visit this link for a nice printable version
  • Press the copy icon on the upper right of code blocks to copy the code

Errors

Types of errors

These are common to all programming languages:

  • Syntax errors
  • Runtime errors
  • Logic errors

Syntax errors

Syntax errors

Each programming language has syntactic rules. If the rules aren't followed, the program cannot be parsed and will not be executed at all.

Spot the syntax errors:


            def add(num1, num2) # Missing colon
               return num1 + num2
            

            name = 'bruno'
            name + = 'no' # No space needed between + and =
            

To fix a syntax error, read the message carefully and go through your code with a critical eye. 👁

SyntaxError

What it technically means:
The file you ran isn’t valid python syntax

What it practically means:
You made a typo

What you should look for:

  • Extra or missing parenthesis
  • Missing colon at the end of an if, while, def statements, etc.
  • You started writing a statement but forgot to put any clauses inside

Examples:


            print("just testing here"))
            

            title = 'Hello, ' + name ', how are you?'
            

IndentationError/TabError

What it technically means:
The file you ran isn't valid Python syntax, due to indentation inconsistency.

What it sometimes means:
You used the wrong text editor (or one with different settings)

What you should look for:

  • A typo or misaligned block of statements
  • A mix of tabs and spaces
    • Open your file in an editor that shows them
    • cat -A filename.py will show them

Example:


            def sum(a, b):
                total = a + b
               return total
            

Runtime errors

Runtime errors

A runtime error happens while a program is running, often halting the execution of the program. Each programming language defines its own runtime errors.

Spot the runtime error:


            def div_numbers(dividend, divisor):
                return dividend/divisor

            quot1 = div_numbers(10, 2)
            quot2 = div_numbers(10, 1)
            quot3 = div_numbers(10, 0)  # Cannot divide by 0!
            quot4 = div_numbers(10, -1)
            

To prevent runtime errors, code defensively and write tests for all edge cases.

Tracebacks

When there's a runtime error in your code, you'll see a traceback in the console.


                def div_numbers(dividend, divisor):
                    return dividend/divisor

                quot1 = div_numbers(10, 2)
                quot2 = div_numbers(10, 1)
                quot3 = div_numbers(10, 0)
                quot4 = div_numbers(10, -1)
            

            Traceback (most recent call last):
                File "main.py", line 14, in <module>
                    quot3 = div_numbers(10, 0)
                File "main.py", line 10, in div_numbers
                    return dividend/divisor
            ZeroDivisionError: division by zero
            

Parts of a Traceback

  • The error message itself
  • Lines #s on the way to the error
  • What’s on those lines

The most recent line of code is always last (right before the error message).


            Traceback (most recent call last):
                File "main.py", line 14, in <module>
                    quot3 = div_numbers(10, 0)
                File "main.py", line 10, in div_numbers
                    return dividend/divisor
            ZeroDivisionError: division by zero
            

Reading a Traceback

  1. Read the error message (remember what common error messages mean!)
  2. Look at each line, bottom to top, and see if you can find the error.

            Traceback (most recent call last):
                File "main.py", line 14, in <module>
                    quot3 = div_numbers(10, 0)
                File "main.py", line 10, in div_numbers
                    return dividend/divisor
            ZeroDivisionError: division by zero
            

More runtime errors

Now that we know how to read a traceback, let's look at a few common runtime errors.

TypeError:'X' object is not callable

What it technically means:
Objects of type X cannot be treated as functions

What it practically means:
You accidentally called a non-function as if it were a function

What you should look for:

  • Parentheses after variables that aren't functions

Example:


            sum = 2 + 2
            sum(3, 5)
            

...NoneType...

What it technically means:
You used None in some operation it wasn't meant for

What it practically means:
You forgot a return statement in a function

What you should look for:

  • Functions missing return statements
  • Printing instead of returning a value

Example:


            def sum(a, b):
                print(a + b)

            total = sum( sum(30, 45), sum(10, 15) )
            

NameError

What it technically means:
Python looked up a name but couldn't find it

What it practically means:

  • You made a typo
  • You are trying to access variables from the wrong frame

What you should look for:

  • A typo in the name
  • The variable being defined in a different frame than expected

Example:


            fav_nut = 'pistachio'
            best_chip = 'chocolate'
            trail_mix = Fav_Nut + best__chip
            

Logic errors

Logic errors

A program has a logic error if it does not behave as expected. Typically discovered via failing tests or bug reports from users.

Spot the logic error:


            def calculate_lifetime_supply(current_age, amount_per_day):
                """ Returns the amount of items consumed over a lifetime
                (with a max age of 100 assumed) based on the current age
                and the amount consumed per day.

                >>> calculate_lifetime_supply(99, 1)
                365
                >>> calculate_lifetime_supply(99, 2)
                730
                """
                return amount_per_day * (100 - current_age)
            

To avoid the wrath of angry users, write tests.

Debugging tools

Buggy code

These tools are particularly helpful for debugging logical bugs.

Let's start with this buggy code:


            def suggest_supplies(temperature, windspeed, incline):
                """
                >>> suggest_supplies(33, 3, 0)
                ''
                >>> suggest_supplies(33, 3, 5)
                'stick '
                >>> suggest_supplies(33, 6, 0)
                'windbreaker '
                >>> suggest_supplies(17, 0, 0)
                'thermal '
                >>> suggest_supplies(33, 6, 10)
                'windbreaker stick '
                >>> suggest_supplies(17, 0, 10)
                'stick thermal '
                """
                supplies = ''
                if windspeed > 5:
                    supplies += 'windbreaker '
                elif incline > 0:
                    supplies = 'stick '
                elif temperature < 32:
                    supplies += 'thermal '
                return supplies
            

Print debugging

A strategy that can be used across programming languages is to put print() statements in your code.

Two goals:

  • Verify whether a particular line of code was executed.
  • Monitor the values at particular points.

            ...
            elif incline > 0:
                print("here! incline > 0")
                supplies = 'stick '
                print("supplies", supplies)
            ...
            

PythonTutor

A tool designed especially for understanding the execution of Python code.

Screenshot of PythonTutor

Breakpoint debugging

Set breakpoints in an IDE and step line-by-line through the code that follows.

Screenshot of VS Code with debugger enabled for Python file

For VS Code, install the Python extension first.