I've been trying to use line_profiler
on a wrapped function. However, the wrapper function gets profiled instead of the wrapped function. The executable snippet below shows that I can see that my @decorate
wrapper function gets profiled instead of my d()
function.
Snippet: testy_profiler.py
from line_profiler import LineProfiler
def a():
print("a")
from functools import wraps
def decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
r = func(*args, **kwargs)
print("decorating")
return r
return wrapper
@decorate
def d():
print("d")
def go():
a()
d()
lprofiler = LineProfiler()
lprofiler.add_function(a)
lprofiler.add_function(d)
lp_wrapper = lprofiler(go)
lp_wrapper()
lprofiler.print_stats()
Output:
$ python test_profiler.py
a
d
decorating
Timer unit: 1e-07 s
Total time: 1e-05 s
File: C:\Users\RENARDCH\Workspace\testy_profiler\test_profiler.py
Function: a at line 3
Line # Hits Time Per Hit % Time Line Contents
==============================================================
3 def a():
4 1 100.0 100.0 100.0 print("a")
Total time: 4.4e-06 s
File: C:\Users\RENARDCH\Workspace\testy_profiler\test_profiler.py
Function: wrapper at line 8
Line # Hits Time Per Hit % Time Line Contents
==============================================================
8 @wraps(func)
9 def wrapper(*args, **kwargs):
10 1 24.0 24.0 54.5 r = func(*args, **kwargs)
11 1 18.0 18.0 40.9 print("decorating")
12 1 2.0 2.0 4.5 return r
Total time: 1.87e-05 s
File: C:\Users\RENARDCH\Workspace\testy_profiler\test_profiler.py
Function: go at line 19
Line # Hits Time Per Hit % Time Line Contents
==============================================================
19 def go():
20 1 126.0 126.0 67.4 a()
21 1 61.0 61.0 32.6 d()
This especially poses problem when two functions are wrapped with the same wrapper, since a global profiling will be generated for the wrapper, instead of a profiling for each functions individually.
line_profiler
version is 3.4.0
According to this issue, line_profiler will not profile code inside a decorated function, and will instead profile the decorator.
There is a workaround, however.
If you want to profile the function which the decorator calls, you can do this:
lprofiler.add_function(d.__wrapped__)
Since you are using functool.wraps()
, that sets the __wrapped__
attribute to point to the original function. Many other Python decorators set __wrapped__
too, though it is not a requirement. More information can about __wrapped__
can be found here.