装饰器来代替整个方法的特定代码行?

时间:2015-05-25 08:17:01

标签: python

让我们假设一个简单的方法:

ST_SetSRID(ST_point(x, y), 12345)

要使用装饰器计时此方法,一个简单的装饰器将是:

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

现在,如果我想计算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 第4行test_method的特定行,那么当前的实现涉及内联编码,如:

sum1 = sum(range(a,b))

目的是使用装饰器来定时特定方法的行M到N而不修改方法中的代码。 是否可以使用装饰器注入这样的逻辑?

5 个答案:

答案 0 :(得分:11)

您可以使用上下文管理器。

import contextlib

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

在您的代码中,您可以像

一样使用它
with time_measure('test_method:sum1'):
    sum1 = sum(range(a, b))

顺便说一句,如果您想改进代码,可以使用高斯求和公式(解释为here)而不是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

答案 1 :(得分:1)

我能想到的一种方法是在跟踪器函数中处理“line”事件时使用sys.settrace()并记录时间。但有一点需要注意的是,设置跟踪器的做法可能会导致记录的时间不准确。

一般的想法是:

  1. 在封装目标方法的装饰器中设置跟踪器函数。
  2. 使用FLN = inspect.currentframe().f_lineno.
  3. 获取此方法第一行的行号
  4. 在跟踪器函数中,处理“call”事件并返回本地跟踪器函数以跟踪范围中的“line”事件。 Read this如果你感到困惑。
  5. 在本地跟踪器功能中,获取当前行号LN, 如果LN-FLN == M,则记录开始时间;如果LN-FLN == N,记录结束时间,执行M到N行所用的时间是结束时间 - 开始时间。
  6. 代码:

    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()
    

答案 2 :(得分:1)

它非常难看,而且代码不是很稳定。但我发现执行此任务的唯一方法是在注入代码后再次执行该函数的代码 像这样:

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)

答案 3 :(得分:0)

装饰器只能装饰callables(例如函数,方法,类)。只要您不将它们包装在自己的可调用对象中,单行或一组行就不可调用。

对于代码的单位计时,您应该选择适当的重复次数。目标是确保执行时间长于几微秒或几毫秒,否则测量误差将太大。

你看过timeit模块了吗?

答案 4 :(得分:0)

这里是@NiklasR的答案的略有变化,其中没有logger,但有print,还有一个现成的示例:

import contextlib, time

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

with time_measure('hello'):
    sum1 = sum(x ** 2 for x in range(1, 1000000))

# hello: 0.577033281326294 ms