Skip to content

Instantly share code, notes, and snippets.

@kousu
Last active February 7, 2020 09:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kousu/5a83e248b230cdafad076c0c682888ec to your computer and use it in GitHub Desktop.
Save kousu/5a83e248b230cdafad076c0c682888ec to your computer and use it in GitHub Desktop.
Better python breakpoint()
# Try 'python test.py' with the above breakpoint hook loaded. Try adding/removing/editing the variables.
def g(a,b,c):
print(locals())
print()
print(globals())
print()
a+=b+c
print("Before the breakpoint", locals())
breakpoint()
print("After the breakpoint", locals())
return c
def h():
y = 4
print("g = ", g(2,y,6))
return y*3
h()
# I bequeth any rights I might have in this to the public domain
# Put this in ~/.local/lib/python3.7/site-packages/usercustomize.py
# and then breakpoint() will be plainer -- it will be a standard python REPL.
# (*caveat: tab completion isn't fully the same as cpython's built-in thing)
print("REMEMBER YOU HAVE THE WEIRD BREAKPOINT LOADED")
def breakpoint(*args, **kwargs):
"""
Set breakpoint() to drop into the interactive interpreter
the default breakpoint() drops to Pdb, which can then get to the interpreter via 'interact', but without tab completion, so it's not really the same
Bug: it doesn't know to restrict tab-completing imports to only modules, or how to tab-complete dots.
and maybe other quirks
"""
import sys
frame = sys._getframe(1) # experimentally, 1 frame back is the right number of frames
# https://stackoverflow.com/questions/35115208/is-there-any-way-to-combine-readline-rlcompleter-and-interactiveconsole-in-pytho
import readline
from rlcompleter import Completer
readline.parse_and_bind("tab: complete")
readline.set_completer(Completer(frame.f_locals).complete) # TODO: expand the set of completions
# stackoverflow.com/a/1396386/2898673
import code
# asdsfgsd InteractiveConsole is broken. it claims to take 'locals' but it actually passes them through to exec() as the *globals* argument
# and exec() has a bunch of automatic things it needs to do (for one: fill in __builtins__ if not otherwise there, otherwise no code will work at all)
f_locals = frame.f_locals # from pdb.py:
# The f_locals dictionary is updated from the actual frame
# locals whenever the .f_locals accessor is called, so we
# cache it here to ensure that modifications are not overwritten.
class InteractiveConsole(code.InteractiveConsole):
def runcode(self, code):
try:
exec(code, frame.f_globals, f_locals)
except SystemExit:
raise
except:
self.showtraceback()
InteractiveConsole(locals=None).interact(banner="", exitmsg="") # Note: we've made locals irrelevant
# Next level: re-export the variables, if changed
# Pdb can't even do this; can ipython?
# based on https://stackoverflow.com/questions/34650744/modify-existing-variable-in-locals-or-frame-f-locals / http://pydev.blogspot.com/2014/02/changing-locals-of-frame-frameflocals.html
# This is cpython-specific!!
# Every access frame.f_locals *is a getter function* secretly, and every access to it refreshes the contents from the internal cpython data "fast" cache
# to go the other way you need to call PyFrame_LocalsToFast *before* each write
# ...or..something?
# Surprise feature: `del` gets passed through immediately to the underlying storage
import ctypes
# LocalsToFast() syncs the state of f_locals with the internal cpython VM
# To take effect, it seems to need to be called before *each* write
# it also needs to be called at least once, to handle synchronize when all variables were deleted,
# hence this weird do-while mess.
ctypes.pythonapi.PyFrame_LocalsToFast(
ctypes.py_object(frame),
ctypes.c_int(1))
for k, v in f_locals.items():
frame.f_locals[k] = v
ctypes.pythonapi.PyFrame_LocalsToFast(
ctypes.py_object(frame),
ctypes.c_int(1))
import sys
sys.breakpointhook = breakpoint
del sys
del breakpoint
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment