I ran into an interesting issue when debugging in Python that I thought was worth sharing. It came up when I was visualizing some results from an object detector. I had a class for an object detector and one of the things it would check was that each prediction was associated with a valid object in the object dictionary. Here is a mock-up of the relevant parts (this is not the real class, just a toy example so as not to distract from the point).
class ObjectDetector():
def __init__(self, object_map):
self.object_map = object_map
self.predictions = self.get_predictions()
assert all([pred in self.object_map for pred in self.predictions])
def get_predictions(self):
"Spoofed result"
return [1, 1, 1, 1, 1, 2, 2, 2, 3, 3]
object_map = {0: 'cat', 1: 'dog', 2: 'bird'}
od = ObjectDetector(object_map)
print(od.predictions)
After running the code, I got the following error:
$ python object_detector.py
Traceback (most recent call last):
File "object_detector.py", line 13, in <module>
od = ObjectDetector(object_map)
File "object_detector.py", line 6, in __init__
assert all([pred in self.object_map for pred in self.predictions])
AssertionError
Interesting. I wasn’t sure what the problem was so I decided to drop in a debugger. Immediately before the assert statement, I dropped in import ipdb; ipdb.set_trace()
.
When I debug, I like to go nice and easy. Step 1 is to recreate the error message and take it from there. Actually, I should call it step 0 because it couldn’t possibly go wrong (yes, you know what’s going to happen next).
ipdb> assert all([pred in self.object_map for pred in self.predictions])
*** NameError: name 'self' is not defined
What? I wanted an AssertionError
, not a NameError
. How did that happen? Where did self
go?
ipdb> self
<__main__.ObjectDetector object at 0x00000220AB590CC8>
ipdb> self.object_map
{0: 'cat', 1: 'dog', 2: 'bird'}
Oh, it’s right where I left it. So what happened before? Maybe I’m missing something else? Let’s see if this works:
ipdb> self.predictions[0] in self.object_map
True
OK, all looks good. Now I’ll just…
ipdb> [pred in self.object_map for pred in self.predictions]
*** NameError: name 'self' is not defineds]
Arrgh! I was trying to sneak that one by. I was pretty stuck at this point, but I’ll spare you the head-scratching and the old standby: turn-it-off-and-turn-it-back-on-again. The key here is to take a look at globals
and locals
.
ipdb> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000220AB28D488>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'object_detector.py', '__cached__': None, 'ObjectDetector': <class '__main__.ObjectDetector'>, 'object_map': {0: 'cat', 1: 'dog', 2: 'bird'}}
ipdb> locals()
{'object_map': {0: 'cat', 1: 'dog', 2: 'bird'}, 'ipdb': <module 'ipdb' from 'C:\\Users\\Julius\\anaconda3\\envs\\tf\\lib\\site-packages\\ipdb\\__init__.py'>, 'self': <__main__.ObjectDetector object at 0x00000220AB590CC8>}
That’s odd… why is self
in locals
but not in globals
? It should be in globals
. Fortunately, I can add it with…
globals().update(locals())
Then check it again…
ipdb> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000220AB28D488>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'object_detector.py', '__cached__': None, 'ObjectDetector': <class '__main__.ObjectDetector'>, 'object_map': {0: 'cat', 1: 'dog', 2: 'bird'}, 'ipdb': <module 'ipdb' from 'C:\\Users\\Julius\\anaconda3\\envs\\tf\\lib\\site-packages\\ipdb\\__init__.py'>, 'self': <__main__.ObjectDetector object at 0x00000220AB590CC8>}
There it is. Now we can…
ipdb> [pred in self.object_map for pred in self.predictions]
[True, True, True, True, True, True, True, True, False, False]
Ah, that’s better. From here on out it’s simple to debug.
ipdb> self.predictions
[1, 1, 1, 1, 1, 2, 2, 2, 3, 3]
Hmm… it’s the “3”s that are the problem.
ipdb> self.object_map.keys()
dict_keys([0, 1, 2])
D’oh! I 0-indexed the objects in the object_map
but not in the predictions
. The original bug was a simple one and easy to fix, but the missing self
definitely confounded me for a bit.
I found (afterwards, of course) that this had been reported way back in 2010! It’s been reported a few times to different debuggers and is an issue for dictionary comprehensions as well. There’s a bit of a deeper discussion in the threads if you’re interested, but, in summary, it’s actually a Python issue, and not specific to any debugger. And based on how long it’s been open, I don’t expect it to be fixed any time soon. Just remember, globals().update(locals())
is your friend.