Skip to content

Error checking and debugging

If there is a problem in some Python code being run it will often stop and print out an error message to the screen. This is called "raising" an error, also known as an exception. The final line of the error message will be the type of exception that has been raised. Preceding this will be a "traceback" showing where the error occurred in the code, often with a line number, and can be nested down through the chain of functions calling the buggy code. This chain of functions may be ones that you have defined or functions within a built-in or user installed module.

These errors are often not that informative for novices (or indeed experts), especially at first glance. But looking at the exception type can give hints as to the cause of the problem.

Common exceptions

A non-exhaustive list of errors and some reasons for them is given below.

Syntax errors

If code contains invalid Python syntax then a SyntaxError may be raised, often when importing a module containing a bug. The cause of such an error is often:

  • forgetting to put a colon : on a line defining a function, class or flow control statement;
  • forgetting to close a set of open brackets (it can be tricky keeping track of open and closing bracket in some statements).

Many Python editors, including VS Code, will highlight the associated pair of opening and closing brackets if you click on one of them. This can help finding missing brackets.

Forgotten colon

def myfunc()  # look, no colon!
    return "I should raise an error if imported!"
from showsyntaxerror_colon import myfunc
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/matthew/repositories/PHYS281/docs/showsyntaxerror_colon.py", line 1
    def myfunc()  # look, no colon!
                                  ^
SyntaxError: invalid syntax

Forgotten closing bracket

def myfunc():
    # forget to close the last square bracket ]
    x = [y.upper() for y in ["a", "b", "c"]
    return x
from showsyntaxerror_bracket import myfunc
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/matthew/repositories/PHYS281/docs/showsyntaxerror_bracket.py", line 4
    return x
         ^
SyntaxError: invalid syntax

Often when exceptions are raised due to not closing brackets the error message will pick out the line after the one containing the invalid statement. Above it shows the error coming from the return line rather than the x definition line.

Import errors

Trying to import a module that is not installed or present in your path will result in an ImportError or a ModuleNotFoundError:

import blahblahblah
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'blahblahblah'

Indentation errors

If a function, class or flow-control definition contains inconsistent indentation it will result in an IndentationError.

def myfunc():
    x = 1  # indented with four spaces
     y = 2  # indented with 5 spaces!

    return x + y
from indentationerror import myfunc
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/matthew/repositories/PHYS281/docs/indentationerror.py", line 3
    y = 2  # indented with 5 spaces!
    ^
IndentationError: unexpected indent

Index errors

For array-like objects, e.g., lists, trying to access an element that is outside its range will raise an IndexError:

x = [1, 2, 3]
print(x[5])  # try accessing 6th value, which does not exist
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

Key errors

For dictionary objects, trying to access a key that does not exist will raise a KeyError:

y = {"a": 1, "b": 2}
y["c"]  # try accessing "c" key that does not exist
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'c'

Name errors

Trying to use a variable name before it has been defined will raise a NameError:

print(z)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'z' is not defined

Attribute errors

Trying to use an attribute of an object, when it is not defined by that object's class, will raise an AttributeError:

x = 1
x.hello  # integers have no hello attribute!
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute 'hello'

Type errors

Passing a variable to a class or function that is not of the required type, or trying to get an index from a variable that is not array-like, or trying to use a variable as a function when it's not a function, will often raise a TypeError:

import math
math.sqrt("a")  # the square root of a letter!!!
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: must be real number, not str
x = 2
x[1]  # try and get an index position from an integer!
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not subscriptable
x = 3.4
x()  # try and call x as if it were a function/callable
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'float' object is not callable

IO errors

Trying to open a file that does not exist will raise a FileNotFoundError:

# try opening a non-existent file
with open("blah.txt", "r") as fp:
    fp.readlines()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'blah.txt'

Adding exception-handling to code

Sometimes you can anticipate that a user of your code might get something wrong, e.g., pass a variable of the wrong type to a function. When writing the function you can add checks and raise errors, e.g.:

def i_only_like_ints(x):
    # check x is an integer
    if not isinstance(x, int):
        # raise a TypeError and give an informative message
        raise TypeError("I really do only like integers!")

    return "Thank you for the integer"

i_only_like_ints(2.3)  # give it a float
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in i_only_like_ints
TypeError: I really do only like integers!

You can also "catch" exceptions raised by other functions and handle them how you want. For example, you may not want your code to fail if a certain exception is raised, but instead do something different:

def i_am_ok_with_any_number(x):
    try:
        # try converting x to a complex number type
        complex(x)
    except ValueError:  # "catch" if this raises a ValueError
        # print a message but don't fail
        print(f"Please give me a number not a {type(x)}")
        return

    return f"Thank you for the number: {x}"

Debugging tips

Code will almost inevitably contain bugs at first. Debugging is the process of tracking down and fixing any bugs. This often takes more time than writing the code in the first place!

To help with debugging it is useful to have well-documented code so you can understand what each section is supposed to do.

Here are a few general tips for debugging:

  • Read the output error message, it may be more informative than you first think (see above).
  • Check that all variables that you are trying to use are defined and within scope.
  • Check that all variables are of the expected type and shape that you want. You can use print(type(x)), where x is your variable name, to find the type.
  • Check that any for or while loops exit when expected; do your while loops have a break condition and increment any counters correctly?
  • To find out where a code is failing, or if there are any problematic variables, add print statements at various places within your code. If a print statement is not reached you know the error occurred before it in the code. Doing this iteratively allows you to home in on a problem. Remember to remove the print statements (unless they are generally useful) after debugging is complete.
  • Use Google! Your problem is probably not unique, so someone else may have come across it before and asked "the internet". Sometimes you can just cut and paste an error or part of an error into Google, but other times you will have to think a bit about how to phrase your query with some context. You will learn to hone your Google-fu and pose better questions.
  • Use StackOverflow. This a dedicated question and answer site for programming issues and many Google queries will point you to this site. If asking questions on this site they should be specific and if possible include a short reproducible snippet of code that replicates your problem. Do not use StackOverflow to try and get the answer to your assignments. Do not post questions containing very large chunks of code with ambiguous requests like "Why does my code not work?". Be aware, StackOverflow is a community of volunteers, and some members are more courteous than others!

"Rubber duck" debugging

To make sure you understand what your code is doing, compared to what it is supposed to do, it can be useful to walk through your code section by section describing it to, e.g., a rubber duck 🦆.