How to understand the Python Traceback





Python prints a traceback when an exception is raised in your code.

The said traceback can be a little bit worrying if it's the first time you're seeing it. You will feel very confused and you will not know what it's telling you.

But, the Python traceback has a lot of information that can help you diagnose and fix the problem.

And the first step to becoming a great Python developer is to understand the Python traceback.

What is a Python traceback?

Basically, a traceback is kind of a report that contains the function calls you made in your code at a very specific point.

Tracebacks are known by a lot of names, such as:

  • Stack track
  • Stack traceback
  • Backtrace
  • And others.

But in Python, the most common term is traceback.

Once your program results in an exception, Python prints out the current traceback to help you know what's wrong. Here's an example:

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

In this example, greet() gets called with the parameter someone. But, in greet(), that variable is never used. Instead, it's spelled wrongly as someon in the print() call.



When you run this program, you might get the following traceback and it can be hard to understand the Python traceback.:

$ 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 has a lot of useful information that will help you diagnose the problem. The final line clearly outputs that the name ‘someon' does not exist (not defined).

In the above traceback, the exception was a NameError, which lets you understand that there is a reference to some name that was never defined. In our case the name is someon.

The final line in our case has more than enough information to let us know where the problem is. Once you know that someon is not defined, you'll search the code for this name and you will be right back on track.

But, more often than not, your code is a lot more complicated than this.

How to read a Python traceback?

The Python traceback has a lot of useful information that helps you while trying to determine the reasons for an exception to be raised in your code.

In this section, we'll walk through a couple of different tracebacks to get you on the right verge to understand the Python traceback.

Traceback overview

There are various sections of Python traceback that are very important. The diagram below will highlight the said important parts:




Python Traceback Overview

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

  1. Blue box: The last line contains the error type. It explains the exception name that was raised.
  2. Green box: After the exception name is the error message. This usually contains helpful information for tracing the reason for the exception being raised.
  3. Yellow box: A little bit further on top, there are different function calls moving from bottom to top, also the most recent to least recent. These calls are usually represented by two-line entries for each call.
  4. Red underline: The red underline is fo these calls the actual code was executed.

There are a couple of differences between traceback output, while you're executing your code in the command-line and running code in the REPL.

Below you can find the very same code from the previous section executed in a REPL and the output of the traceback:

>>> 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 the file names are gone, and instead, you get “<stdin>”. This can confuse you further, making it harder to understand the Python traceback.




This does make sense since you've typed the code in through standard input. Additionally, the executed lines of code are not displayed in the traceback at all.

Very specific traceback walkthrough

Perhaps while going through some very specific traceback output will give you a clearer view to see what information the traceback will give you.

The code below is used to demonstrate the information a Python traceback gives you:

# greetings.py
def who_to_greet(person):
    return person if person else input('Who to greet? ')
def greet(someone, greeting='Hi'):
    print(greeting + ', ' + who_to_greet(someone))
def greet_many(people):
    for person in people:
        try:
            greet(person)
        except Exception:
            print('Hello, ' + 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 then triggers the print().who_to_greet function with the someone value passed in.




And lastly, greet_many() will iterate over the list of people and will call greet(). If there's an exception raised by calling greet(), then a backup greeting will be printed.

The code above does not have any bugs and therefore no exception will be raised as long as the right input is provided.

Additionally, if you add a call to greet() to the bottom of greetings.py and specify a keyword argument that is not expected such as greet(‘Mark', greting='Sup'), then you'll get the following traceback:

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

Once again, with a Python traceback, it's better to work backward and moving up the output.

Starting at the last line of the traceback, you can see that the exception was an obvious TypeError. The message then gives you some great information to follow it. It specifies it to you that the greet() was called with a particular keyword argument that it did not expect. The unknown argument is also specified -> greting. 

The next line up tells you the path to the line that resulted in the exception. In our case, it's the greet() call that we added to the bottom of the greetings.py file.

Moving a little bit up, you can see the line on where the error is. In our case, the file is /greetings.py, and the error is located on line 19. And in our case, because our code is not using any other Python modules, we just kind of see <module> here, which roughly translates that this is the file that is being executed.

With a slightly different file and input, you can see the traceback really pointing you in the right direction in finding the issue.




If you're following along, you should remove the buggy greet() call from the bottom of the greetings.py file we've added, and add the following file to your directory:

# example.py
from greetings import greet
greet(1)

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

$ 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, of course, TypeError again. But, this time the message is a little less helpful than before. It just tells you that somewhere in the code it was expected to receive a string, but got an integer instead.

This can be very confusing if you have a rather larger code to work with, and makes ti even harder to understand the Python traceback.

Also, moving up you will see the line that was executed, then the file and line number of the particular code. This time, instead of <module>, you will get the name of the function that was being executed(greet()).

Moving up again, we will see our problematic greet() call passing inside of it an integer.

Sometimes, after an exception is raised, a different bit of code catches that particular exception and results in an exception too.

In situations like that, Python will output all exceptions tracebacks in the specific chronological order they were received. And through this, ending in the most recently rase exception's traceback.




Since obviously this can be confusing, here;'s an example. Add a call to greet_many() to the bottom of greetings.py file like this

# greetings.py
...
greet_many(['Mark', 'John', 1])

This should result in printing greetings to three different people. But, if you run this code you'll see na example of multiple tracebacks being output, like this:

$ 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(['Mark', 'John', 1])
  File "greetings.py", line 12, in greet_many
    print('Hello, ' + person)
TypeError: must be str, not int

Notice the highlighted line in bold with During the handling… in the output above. In between all of the tracebacks, you'll see this line.

It's a very loud and clear message that while your code was trying to handle the previous exception, it caught another one.

It's important to note that in Python 3 this feature was added. In Python 2 you would only get the last exception's traceback.




You've seen the previous exception before when we called the greet() with an integer. Since we added a 1 to the list of the people to greet, we can except the very same result.

But, interestingly enough, the function greet_many() wraps the greet() call in a try and except block. This is just in case greet() results in an exception being raised, then greet_many() wants to print a default greeting.

The portion of greetins.py is repeated like below:

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

So when the function greet() results in a TypeError because of the bad input (integer), greet_many() handles that exception and tries to print a simple greeting. Above the code ends up result in another, very similar exception. It's still trying to add a string and an integer.

Seeing all those traceback outputs can really boost your knowledge of what might be the real reason of an exception. Sometimes when you see the last exception raised, and it's resulting traceback, you still don't see what's wrong.




In those cases, moving up the previous exceptions usually gives you a clearer view of where the root of the problem is.