icecream
icecream copied to clipboard
Using ic in decorator/wrappers; showing alias function and parameters instead of actual function and passed parameters.
When trying to decorate my functions with ic,
In[0]
from icecream import ic
def my_decorator(func, *args, **kwargs):
def icecream_wrapper(*args, **kwargs):
ic(func(*args))
return icecream_wrapper
@my_decorator
def add_two_numbers(a,b):
return (a+b)
add_two_numbers(1,2)
Out[0]
ic| func(*args): 3
From the above example, instead of printing ic| add_two_numbers(1,2): 3
, it instead uses the aliases in the my_decorator
function instead. Probably not an issue, just thought it could be nice to have. Any help or suggestions? Cheers :)
Try https://github.com/alexmojaki/sorcery and this code:
import sorcery
@sorcery.spell
def print_call(frame_info, x):
print(f"{frame_info.get_source(frame_info.call)}: {x!r}")
return x
def my_decorator(func):
@sorcery.no_spells
def icecream_wrapper(*args, **kwargs):
return print_call(func(*args))
return icecream_wrapper
@my_decorator
def add_two_numbers(a, b):
return (a + b)
add_two_numbers(1, 2)
An important part is @sorcery.no_spells
which tells sorcery to skip icecream_wrapper
when finding the calling frame. Unfortunately it's no longer documented properly.
@alexmojaki any way to integrate the magic (terrible pun intended) of sorcery into icecream so @CJC-ds's decorator example above automagically works as intended?
Yes, this is very simple to reimplement without depending on sorcery, it's just keeping track of a list of code objects to ignore and it looks out for those code objects when looking at frames in the stack before picking one to extract the ic() call from.
An alternative API could be something like ic(func(*args), ignore=[icecream_wrapper])
.
There's a tricky API design problem though. If icecream_wrapper
isn't actually what it seems, e.g. if it's been decorated and replaced with a wrapper function, you ignore the wrong code object without noticing.
@pwwang and I thought about this problem way too much:
- https://github.com/pwwang/python-varname/issues/31
- https://github.com/pwwang/python-varname/pull/32
- https://github.com/pwwang/python-varname/issues/33
- https://github.com/pwwang/python-varname/pull/39
There's a bunch of heuristics you can use to mitigate the problem. But there is no perfect solution.
I think for ic() and its scope limited to debugging, it's probably fine to take the small risk of ignoring the wrong thing.
You can also choose a number of frames to skip, like ic(func(*args), skip=1)
. But in complicated situations this can be tricky for the user to get right.
Or maybe you show everything going back a few frames, like ic(func(*args), depth=3)
:
ic | main(): add_two_numbers(1, 2): func(*args): 3
It's like a mini traceback!