Weekly Python tip: read tracebacks from bottom-to-top
Python has a lot more to offer than most devs pick up on the job, especially with LLMs. Python Morsels fills in those gaps, with short screencasts and hands-on exercises on often-overlooked topics.
Python tip: read tracebacks from bottom-to-top
Unless you've always written correct, fully functional code on the first go, you have inevitably seen a traceback. Tracebacks happen all the time, so it's worth knowing how to read them.
The key is that tracebacks are meant to be read from the bottom up.
Here's the traceback we see when we run a count.py program:
$ python3 count.py 5
Traceback (most recent call last):
File "/home/trey/count.py", line 12, in <module>
main()
~~~~^^
File "/home/trey/count.py", line 9, in main
count_to(stop)
~~~~~~~~^^^^^^
File "/home/trey/count.py", line 4, in count_to
for n in range(1, number+1):
~~~~~~^~
TypeError: can only concatenate str (not "int") to str
Start with the last line
The last line in a traceback is the most important one. It tells you what went wrong: the type of exception (TypeError), and an error message (can only concatenate str (not "int") to str).
Recent Python versions have made these error messages much more helpful. Sometimes that last line even suggests a fix:
NameError: name 'nubmers' is not defined. Did you mean: 'numbers'?
If you're not sure what an error message means, that last line is the one to type into your favorite search engine (my favorites are Kagi followed by Ecosia and DuckDuckGo).
A tip when searching: if the error message includes names from your own code (a variable name, a class name, an attribute name), leave them out of your search, or replace them with a * . Other people's code probably doesn't use the exact same names, so those words will only narrow your results.
Then read upward
The rest of the traceback, above that last line, tells you where things went wrong.
That part is made up of frames, one for each step in the chain of calls that led to the error.
Each frame starts with a line beginning with File, and it shows the file, the line number, the function, and the line of code that was running.
The frame just above the error message is where the exception actually occurred: line 4 of count.py, in the count_to function.
File "/home/trey/count.py", line 4, in count_to
for n in range(1, number+1):
~~~~~~^~
That line of ~ and ^ characters under the code narrows it down even further. The ^ characters point to the exact operation that failed, and the ~ characters underline the rest of that expression.
So in this case, it was the + in number+1 that failed.
Each frame above that one shows the code that called it. On line 9, the ^ characters point to (stop), which means it was the call to count_to that failed. And on line 12, it was the call to main that failed. That's why the first line of a traceback says "most recent call last": the most recent call is shown last, at the very bottom.
Note that the bottom of a traceback won't always be your code. If the exception was raised deep inside some other module, you may need to skip up a few levels to find the code that you wrote.
Also note that the line where the exception occurred isn't always the line that needs fixing. Here's the whole count.py program, which is supposed to print the numbers from 1 up to the number it's given:
import sys
def count_to(number):
for n in range(1, number+1):
print(n)
def main():
stop = sys.argv[1]
count_to(stop)
if __name__ == "__main__":
main()
The count_to function expects to be given an integer, so number+1 isn't really the bug. The bug is one level up, in the main function, which passes a string to count_to. Command-line arguments are always strings, so sys.argv[1] needs to be converted to an integer before it's passed to count_to.
When searching isn't enough
Search engines can only get you so far. They can tell you what an error message means in general, but they usually can't tell you why your code is raising it.
For that, I sometimes turn to an LLM-powered chat tool to help me understand what the error means in the context of my code.
Paste in your traceback and the code it points to (or, if it's an "agentic" tool, give it permission to read the relevant files from the traceback), and ask for an explanation of what's going on. If you still don't understand, keep asking questions until you do.
Just keep in mind that LLMs produce plausible answers, which aren't necessarily correct ones. If an explanation doesn't quite make sense, push back. Ask it to explain further and see whether it sticks with its answer or comes up with a different one. Also note that newer and larger models tend to be much much better at producing correct answers.
Dig into the code yourself
Whether you ask a robot or a human, sometimes an explanation of a problem isn't as satisfying or useful as investigating the problem yourself.
You could dig into what's going on by adding print calls to your code with self-documenting f-strings:
print(f"{number = }")
That prints number = '5', and those quotes show that number points to a string.
You could also call Python's built-in breakpoint function to pause your program and poke around.
Or if you'd like to poke around right where an exception occurred, run your program with python3 -m pdb count.py 5 and type c to continue. When the exception occurs, PDB will drop you into a debugging session at the line that raised the exception.
7 daily tips (mostly) on getting help & debugging
Last week's daily Python tips were mostly about getting help and debugging:
- Tip 285: Learn the common uses of Python's help function
- Tip 286: Know your way around the Python docs
- Tip 287: Read tracebacks from the bottom up
- Tip 288: Show the repr with !r in f-strings
- Tip 289: Add context to exceptions with add_note
- Tip 290: Pretty-print data structures with pp
- Tip 291: Check regex matches with the walrus operator
New this week: podcast interview & 5 Recall packs
I was back on the Teaching Python podcast (episode 164) last week, chatting with Kelly Schuster-Paredes and Julian Sequeira about the science of remembering.
We discussed:
- The testing effect: we learn by pulling information out of our heads, not by putting it in
- Effective studying often feels ineffective: rereading feels more productive than retrieval practice (quizzing yourself), and practicing one topic at a time feels more productive than mixing similar topics together
- Why Daily Recall grades your answers for you: switching back and forth between remembering and self-grading adds friction
- Flashcards need a mental model to hang off of: if you're not using pytest yet, don't study the pytest packs
- LLMs don't make expertise obsolete: autopilot has been around for decades, but planes still have pilots
I also published 5 new Daily Recall packs last week:
- How Daily Recall Works: learn how Daily Recall works by using Daily Recall... does your intuition align with how this feature actually works?
- pathlib Paths: all about building new file paths with Python's pathlib
- pathlib Files and Directories ๐: working with files and directories with pathlib (beyond simple path manipulation)
- Debugging with pdb ๐: memorize the half dozen most useful PDB features... honestly these are pretty much all the PDB features I use!
- sets ๐: learn what sets are most useful for, how set arithmetic works, and the most common set operations
Those last 3 packs are only available with Python Morsels All Access, which you can get for just $240/year.
P.S. I'll be speaking at PyBeach this Saturday in Santa Monica. If you live in SoCal, please come! I'd love to give you a Python Morsels sticker. ๐๐ช