@patch
I’ve found the use of fastcores @patch quite magical for tweaking class methods. I also seem to use it alot for debugging, where I patch a method and insert pdb.set_trace() and then step through to inspect the inputs, outputs etc. However this involves copying the entire method from source code into my notebook, which can get a little messy for larger methods.
patch_debugger
Inspired by @hamelsmu and @KevinB’s dives into fastcore, I’m trying to leverage patch_to to insert pdb.set_trace() directly into the method, while staying in the notebook. I know i should be using an editor to drop this debugging code into the source code I’m trying to debug, but why not stay within the warm comfortable glow of your notebook ![]()
Debugger not quite working as expected
I’ve gotten a fair bit of the way there, to the point where I can insert the new code and patch it to the class. However when the class method is called and the debugger runs, it starts from a funky place in python-land (as opposed to line 2 of my function), and stepping through it brings me to strange and foreign places (and not line 3, 4, 5 etc of my function). Screenshot of a few steps in the debugger:
Help!
Any ideas where this is going wrong? I guess its to do with how I recreated the function with exec, possibly something to do with IPython…but not sure where to go from here…
patch_debugger code
import inspect
import re
from fastcore.foundation import patch_to
def patch_debugger(_cls, _f):
# Get source code as string and split at the end of the signature
_f_source = inspect.getsource(_f)
# Remove any spacing 2+ spaces long
_f_source = re.sub(r'( ){2}', '',_f_source)
# split the function by new line
s_func = _f_source.split('\n')
# Add the class annotation
s_func[0] = s_func[0].replace('self', f'self:{_cls.__name__}')
# define the debugger lines to insert
db = ['import pdb','pdb.set_trace()']
# create list of new function lines
new_func = []
new_func.append(s_func[0])
new_func.extend(db)
new_func.extend(s_func[1:])
# turn list into a single string
nf = new_func[0] + '\n'
for n in new_func[1:]:
nf += ' ' + n + '\n'
print(f'New function created: \n\n{nf}')
# Create a new function from string new_func
exec(f'{nf}', locals(), globals()) # globals()
# create assign an object to our new function
exec(f'j = {_f.__name__}', locals(), globals())
"Decorator: add `f` to the first parameter's class (based on f's type annotations)"
cls = next(iter(j.__annotations__.values()))
#return patch_to(cls)(j)
patch_to(cls)(j)
Our class to debug
class BuggyClass():
def having_fun(self, is_sunny=True):
if is_sunny: print("I'm having fun")
else: print("well I'm still having fun")
Test everything is working
bc = BuggyClass()
bc.having_fun()
I’m having fun
Patch our debugger code into BuggyClass.having_fun
patch_debugger(BuggyClass, BuggyClass.having_fun)
Run the patched method
bc = BuggyClass()
bc.having_fun()
Debugger starts here





