Understanding the Python Traceback

Understanding the Python Traceback

by Chad Hansen Reading time estimate 25m basics python

Python prints a traceback when an exception is raised in your code. The traceback output can be a bit overwhelming if you’re seeing it for the first time or you don’t know what it’s telling you. But the Python traceback has a wealth of information that can help you diagnose and fix the reason for the exception being raised in your code. Understanding what information a Python traceback provides is vital to becoming a better Python programmer.

By the end of this tutorial, you’ll be able to:

  • Make sense of the next traceback you see
  • Recognize some of the more common tracebacks
  • Log a traceback successfully while still handling the exception

What Is a Python Traceback?

A traceback is a report containing the function calls made in your code at a specific point. Tracebacks are known by many names, including stack trace, stack traceback, backtrace, and maybe others. In Python, the term used is traceback.

When your program results in an exception, Python will print the current traceback to help you know what went wrong. Below is an example to illustrate this situation:

Language: Python
# example.py
def greet(someone):
    print('Hello, ' + someon)

greet('Chad')

Here, greet() gets called with the parameter someone. However, in greet(), that variable name is not used. Instead, it has been misspelled as someon in the print() call.

When you run this program, you’ll get the following traceback:

Language: Shell
$ python example.py
Traceback (most recent call last):
  File "/path/to/example.py", line 4, in <module>
    greet('Chad')
  File "/path/to/example.py", line 2, in greet
    print('Hello, ' + someon)
NameError: name 'someon' is not defined

This traceback output has all of the information you’ll need to diagnose the issue. The final line of the traceback output tells you what type of exception was raised along with some relevant information about that exception. The previous lines of the traceback point out the code that resulted in the exception being raised.

In the above traceback, the exception was a NameError, which means that there is a reference to some name (variable, function, class) that hasn’t been defined. In this case, the name referenced is someon.

The final line in this case has enough information to help you fix the problem. Searching the code for the name someon, which is a misspelling, will point you in the right direction. Often, however, your code is a lot more complicated.

How Do You Read a Python Traceback?

The Python traceback contains a lot of helpful information when you’re trying to determine the reason for an exception being raised in your code. In this section, you’ll walk through different tracebacks in order to understand the different bits of information contained in a traceback.

Python Traceback Overview

There are several sections to every Python traceback that are important. The diagram below highlights the various parts:

An example Python traceback with call-outs.

In Python, it’s best to read the traceback from the bottom up:

  1. Blue box: The last line of the traceback is the error message line. It contains the exception name that was raised.

  2. Green box: After the exception name is the error message. This message usually contains helpful information for understanding the reason for the exception being raised.

  3. Yellow box: Further up the traceback are the various function calls moving from bottom to top, most recent to least recent. These calls are represented by two-line entries for each call. The first line of each call contains information like the file name, line number, and module name, all specifying where the code can be found.

  4. Red underline: The second line for these calls contains the actual code that was executed.

There are a few differences between traceback output when you’re executing your code in the command-line and running code in the REPL. Below is the same code from the previous section executed in a REPL and the resulting traceback output:

Language: Python
>>> def greet(someone):
...   print('Hello, ' + someon)
... 
>>> greet('Chad')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in greet
NameError: name 'someon' is not defined

Notice that in place of file names, you get "<stdin>". This makes sense since you typed the code in through standard input. Also, the executed lines of code are not displayed in the traceback.

Specific Traceback Walkthrough

Going through some specific traceback output will help you better understand and see what information the traceback will give you.

The code below is used in the examples following to illustrate the information a Python traceback gives you:

Language: Python
# greetings.py
def who_to_greet(person):
    return person if person else input('Greet who? ')

def greet(someone, greeting='Hello'):
    print(greeting + ', ' + who_to_greet(someone))

def greet_many(people):
    for person in people:
        try:
            greet(person)
        except Exception:
            print('hi, ' + person)

Here, who_to_greet() takes a value, person, and either returns it or prompts for a value to return instead.

Then, greet() takes a name to be greeted, someone, and an optional greeting value and calls print(). who_to_greet() is also called with the someone value passed in.

Finally, greet_many() will iterate over the list of people and call greet(). If there is an exception raised by calling greet(), then a simple backup greeting is printed.

This code doesn’t have any bugs that would result in an exception being raised as long as the right input is provided.

If you add a call to greet() to the bottom of greetings.py and specify a keyword argument that it isn’t expecting (for example greet('Chad', greting='Yo')), then you’ll get the following traceback:

Language: Shell
$ python example.py
Traceback (most recent call last):
  File "/path/to/greetings.py", line 19, in <module>
    greet('Chad', greting='Yo')
TypeError: greet() got an unexpected keyword argument 'greting'

Once again, with a Python traceback, it’s best to work backward, moving up the output. Starting at the final line of the traceback, you can see that the exception was a TypeError. The messages that follow the exception type, everything after the colon, give you some great information. It tells you that greet() was called with a keyword argument that it didn’t expect. The unknown argument name is also given to you: greting.

Moving up, you can see the line that resulted in the exception. In this case, it’s the greet() call that we added to the bottom of greetings.py.

The next line up gives you the path to the file where the code exists, the line number of that file where the code can be found, and which module it’s in. In this case, because our code isn’t using any other Python modules, we just see <module> here, meaning that this is the file that is being executed.

With a different file and different input, you can see the traceback really pointing you in the right direction to find the issue. If you are following along, remove the buggy greet() call from the bottom of greetings.py and add the following file to your directory:

Language: Python
# example.py
from greetings import greet

greet(1)

Here you’ve set up another Python file that is importing your previous module, greetings.py, and using greet() from it. Here’s what happens if you now run example.py:

Language: Shell
$ python example.py
Traceback (most recent call last):
  File "/path/to/example.py", line 3, in <module>
    greet(1)
  File "/path/to/greetings.py", line 5, in greet
    print(greeting + ', ' + who_to_greet(someone))
TypeError: must be str, not int

The exception raised in this case is a TypeError again, but this time the message is a little less helpful. It tells you that somewhere in the code it was expecting to work with a string, but an integer was given.

Moving up, you see the line of code that was executed. Then the file and line number of the code. This time, however, instead of <module>, we get the name of the function that was being executed, greet().

Moving up to the next executed line of code, we see our problematic greet() call passing in an integer.

Sometimes after an exception is raised, another bit of code catches that exception and also results in an exception. In these situations, Python will output all exception tracebacks in the order in which they were received, once again ending in the most recently raise exception’s traceback.

Since this can be a little confusing, here’s an example. Add a call to greet_many() to the bottom of greetings.py:

Language: Python
# greetings.py
...
greet_many(['Chad', 'Dan', 1])

This should result in printing greetings to all three people. However, if you run this code, you’ll see an example of the multiple tracebacks being output:

Language: Shell
$ python greetings.py
Hello, Chad
Hello, Dan
Traceback (most recent call last):
  File "greetings.py", line 10, in greet_many
    greet(person)
  File "greetings.py", line 5, in greet
    print(greeting + ', ' + who_to_greet(someone))
TypeError: must be str, not int

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "greetings.py", line 14, in <module>
    greet_many(['Chad', 'Dan', 1])
  File "greetings.py", line 12, in greet_many
    print('hi, ' + person)
TypeError: must be str, not int

Notice the highlighted line starting with During handling in the output above. In between all tracebacks, you’ll see this line. Its message is very clear, while your code was trying to handle the previous exception, another exception was raised.

You have seen the previous exception before, when you called greet() with an integer. Since we added a 1 to the list of people to greet, we can expect the same result. However, the function greet_many() wraps the greet() call in a try and except block. Just in case greet() results in an exception being raised, greet_many() wants to print a default greeting.

The relevant portion of greetings.py is repeated here:

Language: Python
def greet_many(people):
    for person in people:
        try:
            greet(person)
        except Exception:
            print('hi, ' + person)

So when greet() results in the TypeError because of the bad integer input, greet_many() handles that exception and attempts to print a simple greeting. Here the code ends up resulting in another, similar, exception. It’s still attempting to add a string and an integer.

Seeing all of the traceback output can help you see what might be the real cause of an exception. Sometimes when you see the final exception raised, and its resulting traceback, you still can’t see what’s wrong. In those cases, moving up to the previous exceptions usually gives you a better idea of the root cause.

What Are Some Common Tracebacks in Python?

Knowing how to read a Python traceback when your program raises an exception can be very helpful when you’re programming, but knowing some of the more common tracebacks can also speed up your process.

Here are some common exceptions you might come across, the reasons they get raised and what they mean, and the information you can find in their tracebacks.

AttributeError

The AttributeError is raised when you try to access an attribute on an object that doesn’t have that attribute defined. The Python documentation defines when this exception is raised:

Raised when an attribute reference or assignment fails. (Source)

Here’s an example of the AttributeError being raised:

Language: Python
>>> an_int = 1
>>> an_int.an_attribute
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute 'an_attribute'

The error message line for an AttributeError tells you that the specific object type, int in this case, doesn’t have the attribute accessed, an_attribute in this case. Seeing the AttributeError in the error message line can help you quickly identify which attribute you attempted to access and where to go to fix it.

Most of the time, getting this exception indicates that you are probably working with an object that isn’t the type you were expecting:

Language: Python
>>> a_list = (1, 2)
>>> a_list.append(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'tuple' object has no attribute 'append'

In the example above, you might be expecting a_list to be of type list, which has a method called .append(). When you receive the AttributeError exception and see that it was raised when you are trying to call .append(), that tells you that you probably aren’t dealing with the type of object you were expecting.

Often, this happens when you are expecting an object to be returned from a function or method call to be of a specific type, and you end up with an object of type None. In this case, the error message line will read, AttributeError: 'NoneType' object has no attribute 'append'.

ImportError

The ImportError is raised when something goes wrong with an import statement. You’ll get this exception, or its subclass ModuleNotFoundError, if the module you are trying to import can’t be found or if you try to import something from a module that doesn’t exist in the module. The Python documentation defines when this exception is raised:

Raised when the import statement has troubles trying to load a module. Also raised when the ‘from list’ in from ... import has a name that cannot be found. (Source)

Here’s an example of the ImportError and ModuleNotFoundError being raised:

Language: Python
>>> import asdf
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'asdf'
>>> from collections import asdf
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: cannot import name 'asdf'

In the example above, you can see that attempting to import a module that doesn’t exist, asdf, results in the ModuleNotFoundError. When attempting to import something that doesn’t exist, asdf, from a module that does exists, collections, this results in an ImportError. The error message lines at the bottom of the tracebacks tell you which thing couldn’t be imported, asdf in both cases.

IndexError

The IndexError is raised when you attempt to retrieve an index from a sequence, like a list or a tuple, and the index isn’t found in the sequence. The Python documentation defines when this exception is raised:

Raised when a sequence subscript is out of range. (Source)

Here’s an example that raises the IndexError:

Language: Python
>>> a_list = ['a', 'b']
>>> a_list[3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

The error message line for an IndexError doesn’t give you great information. You can see that you have a sequence reference that is out of range and what the type of the sequence is, a list in this case. That information, combined with the rest of the traceback, is usually enough to help you quickly identify how to fix the issue.

KeyError

Similar to the IndexError, the KeyError is raised when you attempt to access a key that isn’t in the mapping, usually a dict. Think of this as the IndexError but for dictionaries. The Python documentation defines when this exception is raised:

Raised when a mapping (dictionary) key is not found in the set of existing keys. (Source)

Here’s an example of the KeyError being raised:

Language: Python
>>> a_dict['b']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'b'

The error message line for a KeyError gives you the key that could not be found. This isn’t much to go on but, combined with the rest of the traceback, is usually enough to fix the issue.

For an in-depth look at KeyError, take a look at Python KeyError Exceptions and How to Handle Them.

NameError

The NameError is raised when you have referenced a variable, module, class, function, or some other name that hasn’t been defined in your code. The Python documentation defines when this exception is raised:

Raised when a local or global name is not found. (Source)

In the code below, greet() takes a parameter person. But in the function itself, that parameter has been misspelled to