Python 修饰符将特定于时间的代码行替换为整个方法?

Python 修饰符将特定于时间的代码行替换为整个方法?,python,Python,让我们假设一个简单的方法: def test_method(): a = 1 b = 10000 c = 20000 sum1 = sum(range(a,b)) sum2 = sum(range(b,c)) return (sum1,sum2) 要使用decorator对该方法计时,简单的decorator应该是: from functools import wraps def timed_decorator(f): @wraps(f)

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

def test_method():
    a = 1
    b = 10000
    c = 20000
    sum1 = sum(range(a,b))
    sum2 = sum(range(b,c))
    return (sum1,sum2)
要使用decorator对该方法计时,简单的decorator应该是:

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
现在,如果我想对
test\u方法的特定时间行
说第4行
sum1=sum(范围(a,b))
,当前的实现包括内联编码,如:

 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)
其目的是使用decorator对特定方法的M到N行进行计时,而不修改方法中的代码。
是否可以使用decorator注入这样的逻辑?

decorator只能修饰可调用项(例如函数、方法、类)。一行或一组行是不可调用的,只要您不将它们包装在它们自己的可调用文件中

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


您看过
timeit
模块了吗?

您可以使用上下文管理器

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

顺便说一句,如果你想改进你的代码,你可以使用高斯和公式(解释)而不是
Sum(范围(a,b))


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

总的想法是:

  • 在包装目标方法的装饰器中设置跟踪函数
  • 使用
    FLN=inspect.currentframe().f_lineno.
  • 在tracer函数中,处理“call”事件并返回一个本地tracer函数来跟踪作用域中的“line”事件。如果你感到困惑
  • 在本地跟踪函数中,获取当前行号LN, 如果LN-FLN==M,记录开始时间;如果LN-FLN==N,则记录结束时间,执行行M到N所用的时间是endtime-starttime
  • 代码:


    它非常丑陋,代码也不太稳定。但我发现执行此任务的唯一方法是在注入代码后再次执行函数的代码。
    大概是这样的:

    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)
    

    具有自定义上下文管理器的非常简单的解决方案:

    class elapsed:
        def __enter__(self): self.start = time.time()
        def __exit__(self, *args): print("%.1f ms" % ((time.time() - self.start)*1000))
    
    用法示例:

    with elapsed():
        sum1 = sum(x ** 2 for x in range(1, 1000000))
    # 547.0 ms
    
    关于这方面的更多信息:


    另一个解决方案:这里是@NiklasR答案的一个细微变化,没有
    记录器
    ,但是
    打印
    ,以及一个准备运行的示例:

    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
    

    同意
    可调用项
    ,但有没有办法访问装饰器内可调用项的M到N行?没有,没有机制指定单元U的“N到M行”。Python解释器不跟踪单个代码单元内的行计数。这将是巨大的开销。我正在寻找一种方法,在不修改原始方法的情况下对这些行进行计时。如果不修改原始代码,就无法做到这一点。从逻辑上判断,只有当它是一个完整的单元时,你才可以在外部进行时间编码,比如
    模块
    函数
    ,等等。至于仅仅是一些代码行,@Niklas R提供了一种很好的和python式的方法来实现你的目标。
    with elapsed():
        sum1 = sum(x ** 2 for x in range(1, 1000000))
    # 547.0 ms
    
    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