timeit与时间装饰器

unu*_*tbu 55 python timing timeit

我正在尝试计算一些代码.首先我用了一个时间装饰器:

#!/usr/bin/env python

import time
from itertools import izip
from random import shuffle

def timing_val(func):
    def wrapper(*arg, **kw):
        '''source: http://www.daniweb.com/code/snippet368.html'''
        t1 = time.time()
        res = func(*arg, **kw)
        t2 = time.time()
        return (t2 - t1), res, func.__name__
    return wrapper

@timing_val
def time_izip(alist, n):
    i = iter(alist)
    return [x for x in izip(*[i] * n)]

@timing_val
def time_indexing(alist, n):
    return [alist[i:i + n] for i in range(0, len(alist), n)]

func_list = [locals()[key] for key in locals().keys()
             if callable(locals()[key]) and key.startswith('time')]
shuffle(func_list)  # Shuffle, just in case the order matters

alist = range(1000000)
times = []
for f in func_list:
    times.append(f(alist, 31))

times.sort(key=lambda x: x[0])
for (time, result, func_name) in times:
    print '%s took %0.3fms.' % (func_name, time * 1000.)
Run Code Online (Sandbox Code Playgroud)

产量

% test.py
time_indexing took 73.230ms.
time_izip took 122.057ms.
Run Code Online (Sandbox Code Playgroud)

在这里我使用timeit:

%  python - m timeit - s '' 'alist=range(1000000);[alist[i:i+31] for i in range(0, len(alist), 31)]'
10 loops, best of 3:
    64 msec per loop
% python - m timeit - s 'from itertools import izip' 'alist=range(1000000);i=iter(alist);[x for x in izip(*[i]*31)]'
10 loops, best of 3:
    66.5 msec per loop
Run Code Online (Sandbox Code Playgroud)

使用timeit结果实际上是相同的,但使用时序装饰器它看起来time_indexing比快time_izip.

这种差异的原因是什么?

应该相信哪种方法?

如果是这样,哪个?

jon*_*eto 55

使用包装functools来改善Matt Alcock的答案.

from functools import wraps
from time import time

def timing(f):
    @wraps(f)
    def wrap(*args, **kw):
        ts = time()
        result = f(*args, **kw)
        te = time()
        print 'func:%r args:[%r, %r] took: %2.4f sec' % \
          (f.__name__, args, kw, te-ts)
        return result
    return wrap
Run Code Online (Sandbox Code Playgroud)

在一个例子中:

@timing
def f(a):
    for _ in range(a):
        i = 0
    return -1
Run Code Online (Sandbox Code Playgroud)

调用方法f包装@timing:

func:'f' args:[(100000000,), {}] took: 14.2240 sec
f(100000000)
Run Code Online (Sandbox Code Playgroud)

这样做的好处是它保留了原始功能的属性; 也就是说,函数名称和docstring等元数据在返回的函数上被正确保留.

  • 如果您使用的是 Python 3.6 或更高版本,您可以将 print 替换为类似 `print(f'Function {f.__name__} take {te-ts:2.4f} 秒')` 的内容 (11认同)
  • @skjerns我认为您不能假设计时装饰器不会成为任何生产代码的一部分。在生产中记录仪器数据绝对是一件事。由于遵循此最佳实践实际上没有任何缺点,因此我认为最好默认使用“wraps”。 (7认同)
  • `wraps` 将有关内部函数的元数据复制到外部函数。如果没有它,装饰函数对象将引用包装器而不是内部函数。仅当使用使用自省的工具(例如调试器)时,这才是一个问题。例如,如果我们在没有“wraps”的装饰函数上调用“help”,则帮助将位于装饰器而不是内部函数上。 (5认同)
  • 我不明白在这里添加了哪些额外的功能包装。您能对此进行扩展吗? (2认同)
  • @AlanH RTFM;)https://docs.python.org/3/library/functools.html#functools.wraps (2认同)

Mat*_*ock 30

我会使用一个时序装饰器,因为你可以使用注释来围绕你的代码撒上时序,而不是让你的代码混乱时序逻辑.

import time

def timeit(f):

    def timed(*args, **kw):

        ts = time.time()
        result = f(*args, **kw)
        te = time.time()

        print 'func:%r args:[%r, %r] took: %2.4f sec' % \
          (f.__name__, args, kw, te-ts)
        return result

    return timed
Run Code Online (Sandbox Code Playgroud)

使用装饰很容易使用注释.

@timeit
def compute_magic(n):
     #function definition
     #....
Run Code Online (Sandbox Code Playgroud)

或者重新定义您想要的时间功能.

compute_magic = timeit(compute_magic)
Run Code Online (Sandbox Code Playgroud)

  • 我相信在这里使用functools.wraps会有一个很小的改进 (8认同)
  • 出于好奇,这个答案从这里复制了吗?:https://www.andreas-jung.com/contents/a-python-decorator-for-measuring-the-execution-time-of-methods (2认同)

Joc*_*zel 19

使用timeit.不止一次运行测试会给我带来更好的结果.

func_list=[locals()[key] for key in locals().keys() 
           if callable(locals()[key]) and key.startswith('time')]

alist=range(1000000)
times=[]
for f in func_list:
    n = 10
    times.append( min(  t for t,_,_ in (f(alist,31) for i in range(n)))) 

for (time,func_name) in zip(times, func_list):
    print '%s took %0.3fms.' % (func_name, time*1000.)
Run Code Online (Sandbox Code Playgroud)

- >

<function wrapper at 0x01FCB5F0> took 39.000ms.
<function wrapper at 0x01FCB670> took 41.000ms.
Run Code Online (Sandbox Code Playgroud)

  • 作为FYI,timeit还会在测试期间禁用垃圾回收.这可能是另一个问题. (22认同)

小智 9

受到 Micah Smith 的回答的启发,我直接进行了有趣的打印(而不使用日志记录模块)。

下面方便在google colab使用。

# pip install funcy
from funcy import print_durations

@print_durations()
def myfunc(n=0):
  for i in range(n):
    pass

myfunc(123)
myfunc(123456789)

# 5.48 mks in myfunc(123)
# 3.37 s in myfunc(123456789)
Run Code Online (Sandbox Code Playgroud)


den*_*nis 8

我厌倦了from __main__ import foo,现在使用它 - 对于简单的args,%r可以工作,而不是在Ipython中.
(为什么timeit只对字符串有效,而不是thunks/closures,即timefunc(f,任意args)?)


import timeit

def timef( funcname, *args, **kwargs ):
    """ timeit a func with args, e.g.
            for window in ( 3, 31, 63, 127, 255 ):
                timef( "filter", window, 0 )
    This doesn't work in ipython;
    see Martelli, "ipython plays weird tricks with __main__" in Stackoverflow        
    """
    argstr = ", ".join([ "%r" % a for a in args]) if args  else ""
    kwargstr = ", ".join([ "%s=%r" % (k,v) for k,v in kwargs.items()]) \
        if kwargs  else ""
    comma = ", " if (argstr and kwargstr)  else ""
    fargs = "%s(%s%s%s)" % (funcname, argstr, comma, kwargstr)
        # print "test timef:", fargs
    t = timeit.Timer( fargs, "from __main__ import %s" % funcname )
    ntime = 3
    print "%.0f usec %s" % (t.timeit( ntime ) * 1e6 / ntime, fargs)

#...............................................................................
if __name__ == "__main__":
    def f( *args, **kwargs ):
        pass

    try:
        from __main__ import f
    except:
        print "ipython plays weird tricks with __main__, timef won't work"
    timef( "f")
    timef( "f", 1 )
    timef( "f", """ a b """ )
    timef( "f", 1, 2 )
    timef( "f", x=3 )
    timef( "f", x=3 )
    timef( "f", 1, 2, x=3, y=4 )
Run Code Online (Sandbox Code Playgroud)

补充:另请参阅"ipython与主要玩的奇怪技巧",Martelli在running-doctests-through-ipython中


Mic*_*ith 5

这就是您祈祷图书馆提供便携式解决方案的需求类型——DRY!幸运的是funcy.log_durations给出了答案。

从文档复制的示例:

@log_durations(logging.info)
def do_hard_work(n):
    samples = range(n)
    # ...

# 121 ms in do_hard_work(10)
# 143 ms in do_hard_work(11)
# ...
Run Code Online (Sandbox Code Playgroud)

浏览有趣的文档以了解其他变体,例如不同的关键字参数和@log_iter_durations.