The key is for _compute_suggestion_error to avoid including f_builtins in the list of candidates when a NameError is raised from a del statement, which always compiles into a DELETE_* bytecode, which we can check by looking at the bytecode that f_lasti points to.
So the problem can be solved by minimally changing:
d = (
list(frame.f_locals)
+ list(frame.f_globals)
+ list(frame.f_builtins)
)
to (after importing dis):
d = (
list(frame.f_locals)
+ list(frame.f_globals)
)
if not dis.opname[frame.f_code.co_code[frame.f_lasti]].startswith('DELETE_'):
d += frame.f_builtins