David Y.
—I’m new to Python development and want to improve my understanding of stack traces and tracebacks. Often, when a script I’m working on runs into an error, it will crash and print out a long list of line numbers and function names, followed by an error message. Some of these functions are ones I’ve written, but most aren’t. Sometimes, Python will even print out more than one stack trace, accompanied by a line like the following:
During handling of the above exception, another exception occurred:
I understand these stack traces are intended to help with debugging, but I don’t know how to read them or what information they’re supposed to provide. Please help me make sense of stack traces.
A stack trace shows the sequence of function calls leading up to a particular line of code, usually where an exception was thrown. If we think of debugging as the investigation of an error, the stack trace is what allows us to retrace our steps and see exactly how the error arose.
To create a simple example stack trace, we’ll use the following script, which contains a deliberate error:
def divide(a, b): return a / b def calculate(): result = divide(10, 0) print(result) def main(): calculate() main()
When we execute this script, we get the following stack trace:
Traceback (most recent call last): File "/tmp/example.py", line 11, in <module> main() File "/tmp/example.py", line 9, in main calculate() File "/tmp/example.py", line 5, in calculate result = divide(10, 0) ^^^^^^^^^^^^^ File "/tmp/example.py", line 2, in divide return a / b ~~^~~ ZeroDivisionError: division by zero
As we can see, this stack trace shows a sequence of function calls. For each one, we have a file name, line number, and function or module name, as well as the line of code itself.
Reading from top to bottom, this stack trace tells the following story:
main()
function was called on line 11 of the example.py
script.main
, calculate()
was called on line 9.calculate
, divide(10, 0)
was called on line 5, with the intention of storing its return value in result
. The ^
characters indicate that the exception occurred inside divide
, before it could return a value.divide
, an attempt was made to execute a / b
, which resolved to 10 / 0
. The ^
character indicates that the exception resulted from the division operation.If we were to step through this script with a debugger, we would stop at each point detailed above. You can try this by running the following command to open the script in Python’s default debugger, pdb
:
python -m pdb example.py
In the shell that appears, use the command s
(or step
) to repeatedly step through the code until the exception is encountered.
As this is a simple script, our stack trace is also quite simple and only includes code from the script itself. A more complex script, with one or more imported modules, would produce longer stack traces. Depending on the code path that resulted in the exception, these stack traces may include substantial portions of code detailing the sequences of function calls within imported modules. We can read those stack traces in the same way, but it usually saves time to skim past the lines covering our code’s dependencies, assuming we trust them to be substantially bug-free.
For example, here’s a simple script that uses the requests
library:
import requests def retrieve(url): requests.get(url) url = "ABC" print("Retrieving contents of URL...") retrieve(url)
When we execute this script, we get the following output:
Retrieving contents of URL... Traceback (most recent call last): File "/tmp/req.py", line 8, in <module> retrieve(url) File "/tmp/req.py", line 4, in retrieve requests.get(url) File "/usr/lib/python3.12/site-packages/requests/api.py", line 73, in get return request("get", url, params=params, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/requests/api.py", line 59, in request return session.request(method=method, url=url, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/requests/sessions.py", line 575, in request prep = self.prepare_request(req) ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/requests/sessions.py", line 484, in prepare_request p.prepare( File "/usr/lib/python3.12/site-packages/requests/models.py", line 367, in prepare self.prepare_url(url, params) File "/usr/lib/python3.12/site-packages/requests/models.py", line 438, in prepare_url raise MissingSchema( requests.exceptions.MissingSchema: Invalid URL 'ABC': No scheme supplied. Perhaps you meant https://ABC?
Only the first two lines of this stack trace show our own code. The rest of the stack trace shows the execution path through the requests
library. If, as will usually be the case, we don’t have any intention of modifying the requests
library, we can safely ignore these lines in the stack trace, and skip to the error message at the bottom, which informs us that our URL was incorrectly formatted.
To demonstrate a case where more than one stack trace is shown, let’s add some faulty error handling to our division-by-zero script from before.
def divide(a, b): try: return a / b except ZeroDivisionError: return a / c def calculate(): result = divide(10, 0) print(result) def main(): calculate() main()
When this script is run, we should receive the following output:
Traceback (most recent call last): File "/tmp/example.py", line 3, in divide return a / b ~~^~~ ZeroDivisionError: division by zero During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/tmp/example.py", line 14, in <module> main() File "/tmp/example.py", line 12, in main calculate() File "/tmp/example.py", line 8, in calculate result = divide(10, 0) ^^^^^^^^^^^^^ File "/tmp/example.py", line 5, in divide return a / c ^ NameError: name 'c' is not defined
Here, we see two stack traces:
ZeroDivisionError
.NameError
that occurred while handling the first exception.When debugging this code, we should address the second error (NameError
) first. Once that’s done, we can rerun the code to get a full stack trace for the ZeroDivisionError
, as we did initially.
For more about working with stack traces, see this answer on printing stack traces in Python.
Tasty treats for web developers brought to you by Sentry. Get tips and tricks from Wes Bos and Scott Tolinski.
SEE EPISODESConsidered “not bad” by 4 million developers and more than 100,000 organizations worldwide, Sentry provides code-level observability to many of the world’s best-known companies like Disney, Peloton, Cloudflare, Eventbrite, Slack, Supercell, and Rockstar Games. Each month we process billions of exceptions from the most popular products on the internet.
Here’s a quick look at how Sentry handles your personal information (PII).
×We collect PII about people browsing our website, users of the Sentry service, prospective customers, and people who otherwise interact with us.
What if my PII is included in data sent to Sentry by a Sentry customer (e.g., someone using Sentry to monitor their app)? In this case you have to contact the Sentry customer (e.g., the maker of the app). We do not control the data that is sent to us through the Sentry service for the purposes of application monitoring.
Am I included?We may disclose your PII to the following type of recipients:
You may have the following rights related to your PII:
If you have any questions or concerns about your privacy at Sentry, please email us at compliance@sentry.io.
If you are a California resident, see our Supplemental notice.