How do I use line_profiler on a wrapped function?

Question:

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:UsersRENARDCHWorkspacetesty_profilertest_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:UsersRENARDCHWorkspacetesty_profilertest_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:UsersRENARDCHWorkspacetesty_profilertest_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

Asked By: Vulpo

||

Answers:

A bypass is to make d() call a non-wrapped function that actually does the job, and profile this sub-function instead of d().

[...]
@decorate
def d():
    effective_d()

def effective_d():
    print("d")

[...]
lprofiler.add_function(effective_d)
[...]
Answered By: Vulpo

According to this issue, line profiler will not profile code inside a decorated function, and will instead profile the decorator. (This issue is a month old at time of writing, and may have changed by the time you read this answer.)

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.

Answered By: Nick ODell
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.