Decorator for specific lines of code instead of the whole method?

Let's look at a simple method:

def test_method(): a = 1 b = 10000 c = 20000 sum1 = sum(range(a,b)) sum2 = sum(range(b,c)) return (sum1,sum2) 

To use this method with a decorator, a simple decorator would be:

 from functools import wraps def timed_decorator(f): @wraps(f) def wrapper(*args, **kwds): start = time.time() result = f(*args, **kwds) elapsed = (time.time() - start)*1000 logger.debug("f::{0} t::{1:0.2f} ms".format(f.__name__, elapsed)) return result return wrapper 

Now, if I want certain test_method lines test_method indicate line 4 sum1 = sum(range(a,b)) , the current implementation includes inline coding, for example:

  def test_method(): a = 1 b = 10000 c = 20000 start = time.time() sum1 = sum(range(a,b)) # timing specific line or lines elapsed = (time.time() - start)*1000 logger.debug("This part took::{1:0.2f} ms".format(elapsed)) sum2 = sum(range(b,c)) return (sum1,sum2) 

The goal is to use a decorator for timelines from M to N of a particular method without changing the code in the method. Is it possible to introduce such logic using a decorator?

+7
python
source share
4 answers

You can use the context manager.

 import contextlib @contextlib.contextmanager def time_measure(ident): tstart = time.time() yield elapsed = time.time() - tstart logger.debug("{0}: {1} ms".format(ident, elapsed)) 

In your code you use it like

 with time_measure('test_method:sum1'): sum1 = sum(range(a, b)) 

By the way, if you want to improve your code, you can use the Gaussian sum(range(a, b)) Formula (explained here ) instead of sum(range(a, b)) .

 def sum_range(a, b): r_a = (a ** 2 + a) / 2 - a r_b = (b ** 2 + b) / 2 - b return r_b - r_a 
+8
source share

One way I can come up with is to use sys.settrace () and the recording time when processing the "line" event in the trace function. But one caveat is that the practice of setting the indicator can lead to the fact that the recorded time will be inaccurate.

General idea:

  • Set the trace function in the decorator that wraps the target method.
  • Get the line number for the first line of this method, FLN = inspect.currentframe().f_lineno.
  • In the trace function, handle the call event and return the local tracer function to track the line events in the area. Read this if you are confused.
  • Within the local trace function, get the current line number LN, if LN-FLN == M, write down the start time; if LN-FLN == N, write down the end time, the time taken to execute lines M through N is the end time - the start time.

the code:

 import sys from functools import wraps import time import linecache _func_name_ = None _func_ln_ = 0 _start_ = 0 _end_ = 0 _timestamp_ = 0 def trace_calls(frame, event, arg): global _func_name_, _func_ln_ def trace_lines(frame, event, arg): global _timestamp_ if event != 'line': return line_no = frame.f_lineno filename = frame.f_code.co_filename if line_no-_func_ln_ == _start_: _timestamp_ = time.time() print "%d %s TS:%d"%(line_no, linecache.getline(filename, line_no)[:-1], _timestamp_) elif line_no-_func_ln_ == _end_: _timestamp_ = time.time() - _timestamp_ print "%d %s"%(line_no, linecache.getline(filename, line_no)[:-1]) print "Lines %d to %d of %s takes %d seconds."%(_start_, _end_, _func_name_, _timestamp_) if event != 'call': return co = frame.f_code _func_ln_ = frame.f_lineno # record the line number at function entry point func_name = co.co_name if func_name != _func_name_: return return trace_lines def time_lines(start, end): global _start_, _end_ _start_, _end_ = start+1, end+2 # function name takes a line, end is inclusive def inner(f): @wraps(f) def wrapper(*args, **kwargs): global _func_name_ _func_name_ = f.__name__ sys.settrace(trace_calls) f(*args, **kwargs) sys.settrace(None) return wrapper return inner @time_lines(2,4) def tested_func(): print "Enter target function" time.sleep(2) time.sleep(1) time.sleep(3) print "Exit target function" if __name__=="__main__": tested_func() 
+1
source share

This is pretty ugly and not very stable code. but the only way I found this task is to execute the function code again after entering the code.
Something like that:

 import inspect import re import time def inject_timer(f,n,m): codelines = inspect.getsourcelines(f)[0] ident_lvl = re.search("^[ \t]*",codelines[n]).group(0) codelines.insert(n,ident_lvl + "start_longJibrishTo_preventCollision = time.time()\n") codelines.insert(m+2,ident_lvl + "elapsed_longJibrishTo_preventCollision = (time.time() - start_longJibrishTo_preventCollision)*1000\n") codelines.insert(m+3,ident_lvl + """print("f::{0} t::{1:0.2f} ms".format("""+f.__name__+""", elapsed_longJibrishTo_preventCollision))\n""") #print "".join(codelines) exec "".join(codelines) in globals() def test_method(): a = 1 b = 10000 time.sleep(2) c = 20000 sum1 = sum(range(a,b)) sum2 = sum(range(b,c)) return (sum1,sum2) inject_timer(test_method,3,5) 
+1
source share

The decorator can only decorate called objects (for example, functions, methods, classes). A single line or group of lines cannot be called unless you terminate them in your own called.

To select a code unit, you must select the appropriate number of repetitions. The goal is to ensure that the execution time is longer than a few micro- or milliseconds, otherwise the measurement error will be too large.

Have you seen the timeit module?

0
source share

All Articles