Python时间测量函数

139

我想创建一个Python函数,以测试每个函数所花费的时间,并打印出其名称和时间,如何打印函数名称?如果有其他方法,请告诉我。

def measureTime(a):
    start = time.clock() 
    a()
    elapsed = time.clock()
    elapsed = elapsed - start
    print "Time spent in (function name) is: ", elapsed

Python 的性能分析工具可以显示每个函数的名称和所花费的时间。请阅读此处:http://docs.python.org/library/profile.html - Roadmaster
最好使用 timeit 进行计时。它不是完美的,但比你尝试的要好得多,而且使用 timeit 比自己编写更好的代码要容易得多。 - user395760
如何在Python中测量时间经过? - jfs
13个回答

269

首先,我强烈建议使用性能分析器,或者至少使用timeit

然而,如果你想编写自己的计时方法以学习,这里提供了一个使用装饰器开始的地方。

Python 2:

def timing(f):
    def wrap(*args):
        time1 = time.time()
        ret = f(*args)
        time2 = time.time()
        print '%s function took %0.3f ms' % (f.func_name, (time2-time1)*1000.0)
        return ret
    return wrap

使用非常简单,只需使用@timing装饰器:

@timing
def do_work():
  #code

Python 3:

def timing(f):
    def wrap(*args, **kwargs):
        time1 = time.time()
        ret = f(*args, **kwargs)
        time2 = time.time()
        print('{:s} function took {:.3f} ms'.format(f.__name__, (time2-time1)*1000.0))

        return ret
    return wrap

注意,我正在调用f.func_name以获取函数名称的字符串表示(在Python 2中),或者在Python 3中使用f.__name__


4
正中我心 :) ... 但你们说服我使用 Python 分析器。 - Wazery
3
这似乎假设time.time()返回自纪元以来的微秒数?文档表示它返回以秒为单位的时间 https://docs.python.org/2/library/time.html#time.time。 - Rahul Jha
在函数中使用yield后,这种方法无法生效。我该如何继续使用这种方法并使用yield? - jiamo
1
自己编写的缺点是什么?存储一系列经过的时间并检查它们的分布不够简单吗? - 3pitt
不知道该如何调用它。timing("func(s, x, d)")无法工作。 - Peter.k
显示剩余2条评论

64

在使用timeit模块后,我不喜欢它的接口,与以下两种方法相比不够优雅。

以下代码适用于Python3。

装饰器方法

这与@Mike的方法几乎相同。这里我添加了kwargsfunctools包装以使其更好。

def timeit(func):
    @functools.wraps(func)
    def new_func(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        elapsed_time = time.time() - start_time
        print('function [{}] finished in {} ms'.format(
            func.__name__, int(elapsed_time * 1_000)))
        return result
    return new_func

@timeit
def foobar():
    mike = Person()
    mike.think(30)

上下文管理器方法

from contextlib import contextmanager

@contextmanager
def timeit_context(name):
    start_time = time.time()
    yield
    elapsed_time = time.time() - start_time
    print('[{}] finished in {} ms'.format(name, int(elapsed_time * 1_000)))
例如,您可以像这样使用它:
with timeit_context('My profiling code'):
    mike = Person()
    mike.think()

而且在with代码块内的代码将会被计时。

结论

使用第一种方法,您可以轻松地注释掉装饰器以获取普通代码。然而,它只能计时一个函数。如果您有一些不想将其作为函数的代码部分,那么您可以选择第二种方法。

例如,现在您有:

images = get_images()
big_image = ImagePacker.pack(images, width=4096)
drawer.draw(big_image)

现在你想计时big_image = ...这一行。如果你将其改为一个函数,它将变成:

现在您想计时big_image = ...这一行。如果将其改为一个函数,则会变成:

images = get_images()
big_image = None
@timeit
def foobar():
    nonlocal big_image
    big_image = ImagePacker.pack(images, width=4096)
drawer.draw(big_image)

看起来不是很好...如果你在使用没有nonlocal关键字的Python 2版本中,该怎么办。

相反,使用第二种方法非常适合这里:

images = get_images()
with timeit_context('foobar'):
    big_image = ImagePacker.pack(images, width=4096)
drawer.draw(big_image)

有趣的贡献,但我认为你提到的装饰器方法中,必须更改timeit接口并使用functools模块中的wraps()函数是无用的。我的意思是所有额外的代码都是不必要的。 - Billal Begueradj
2
需要 import functools - Guillaume Chevalier
2
请注意,您的修饰器会丢失原始函数的返回值。 - Marc Van Daele
我喜欢使用上下文来进行计时的想法。我想知道是否可以扩展到为重复执行定义一个定时数量。我尝试过了,但它不起作用:@contextmanager def timeit_context(name, repeats=1): startTime = time.time() for ind in range(repeats): yield elapsedTime = time.time() - startTime print('[{}] finished in {} ms'.format(name, elapsedTime * 1000./repeats)) - packoman

14

我不明白timeit模块有什么问题。这可能是最简单的方法。

import timeit
timeit.timeit(a, number=1)

你可以通过装饰器将函数进行封装,并且向函数中传递参数。此处有更详细的解释:http://www.pythoncentral.io/time-a-python-function/

只有需要运行函数一次并获得其返回值时,你可能会对编写自己的计时语句感兴趣。

使用timeit模块的好处是它允许你重复多次执行。这可能是必要的,因为其他进程可能会干扰计时的准确性。因此,你应该运行它多次并查看最低值。


4
使用包装器和装饰器向函数发送参数?为什么不使用timeit.timeit(lambda: func(a,b,c), number=1)?我在终端中测试假设解决方案时会使用这种方法。 - Jacklynn

12

Timeit有两个主要缺陷:它不会返回功能的返回值,并且它使用评估(eval),这需要传入额外的设置代码进行导入。这可以简单而优雅地解决这两个问题:

def timed(f):
  start = time.time()
  ret = f()
  elapsed = time.time() - start
  return ret, elapsed

timed(lambda: database.foo.execute('select count(*) from source.apachelog'))
(<sqlalchemy.engine.result.ResultProxy object at 0x7fd6c20fc690>, 4.07547402381897)

谢谢!timeit在Apache Spark中表现不佳,因为您必须导入所有Spark依赖项,而谁想要制作一个包含所有依赖项的大字符串呢?这个解决方案更简单、更灵活。 - Paul
我认为这个解决方案更接近原始帖子的精神,或者至少是我的阅读方式。所有其他解决方案都有很多样板代码,而在这里,我们清楚地讨论如何通过进行内联更改或尽可能靠近内联来快速获取我的代码单行的时间。 - Rho Phi

4

有一个简单的工具可以进行计时。https://github.com/RalphMao/PyTimer

它可以像装饰器一样工作:

from pytimer import Timer
@Timer(average=False)      
def matmul(a,b, times=100):
    for i in range(times):
        np.dot(a,b)        

输出:

matmul:0.368434
matmul:2.839355

它还可以像一个插件定时器一样工作,具有命名空间控制(如果您将其插入到具有大量代码且可能在其他任何地方调用的函数中,则非常有用)。

timer = Timer()                                           
def any_function():                                       
    timer.start()                                         

    for i in range(10):                                   

        timer.reset()                                     
        np.dot(np.ones((100,1000)), np.zeros((1000,500)))
        timer.checkpoint('block1')                        

        np.dot(np.ones((100,1000)), np.zeros((1000,500)))
        np.dot(np.ones((100,1000)), np.zeros((1000,500)))
        timer.checkpoint('block2')                        
        np.dot(np.ones((100,1000)), np.zeros((1000,1000)))

    for j in range(20):                                   
        np.dot(np.ones((100,1000)), np.zeros((1000,500)))
    timer.summary()                                       

for i in range(2):                                        
    any_function()                                        

输出:

========Timing Summary of Default Timer========
block2:0.065062
block1:0.032529
========Timing Summary of Default Timer========
block2:0.065838
block1:0.032891

希望这可以帮助到您


3

使用Python库进行装饰器方法:

import decorator

@decorator
def timing(func, *args, **kwargs):
    '''Function timing wrapper
        Example of using:
        ``@timing()``
    '''

    fn = '%s.%s' % (func.__module__, func.__name__)

    timer = Timer()
    with timer:
        ret = func(*args, **kwargs)

    log.info(u'%s - %0.3f sec' % (fn, timer.duration_in_seconds()))
    return ret

查看我博客上的文章:

mobilepro.pl 博客上的文章

Google Plus 上我的帖子


这些文章与 IT 技术有关,其中介绍了一个用于计时函数的 Python 装饰器。

2

我的做法:

from time import time

def printTime(start):
    end = time()
    duration = end - start
    if duration < 60:
        return "used: " + str(round(duration, 2)) + "s."
    else:
        mins = int(duration / 60)
        secs = round(duration % 60, 2)
        if mins < 60:
            return "used: " + str(mins) + "m " + str(secs) + "s."
        else:
            hours = int(duration / 3600)
            mins = mins % 60
            return "used: " + str(hours) + "h " + str(mins) + "m " + str(secs) + "s."

在执行函数/循环之前,将变量设置为start = time(),并在块后立即使用printTime(start)

这样你就能得到答案了。


0
以下是一个计时器类:
  • 易于使用:可直接使用或作为装饰函数,代码行数少于100行
  • 测量很多内容:总调用次数、总时间、平均时间和标准差。
  • 输出美观的时间格式
  • 线程安全

以下是使用方法:

# Create the timer
timer1 = Timer("a name", log_every=2)

# Use "with"
with timer1:
   print("timer1")

# Reuse as a decorator
@timer1
def my_func():
  print("my_func")

# Instantiate as a decorator
@Timer("another timer", log_every=1)
def my_func2():
  print("my_func2")

my_func()
my_func2()
my_func()

以下是类的内容
from datetime import datetime
import time, logging, math, threading
class Timer(object):
    '''A general timer class. Does not really belong in a judicata file here.'''
    def __init__(self, name, log_every = 1):
        self.name = name
        self.log_every = 1
        self.calls = 0
        self.total_time = 0
        self.total_squared_time = 0
        self.min, self.max = None, 0
        # Make timer thread-safe by storing the times in thread-local storage.
        self._local = threading.local()
        self._lock = threading.Lock()

    def __enter__(self):
        """Start a new timer"""
        self._local.start = datetime.utcnow()

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Stop the timer, and report the elapsed time"""
        elapsed_time = (datetime.utcnow() - self._local.start).total_seconds()
        with self._lock:
            self.calls += 1
            self.total_time += elapsed_time
            if self.min == None or elapsed_time < self.min:
                self.min = elapsed_time
            if elapsed_time > self.max:
                self.max = elapsed_time
            self.total_squared_time += elapsed_time * elapsed_time
            if self.log_every and (self.calls % self.log_every) == 0:
                self.log()

    def __call__(self, fn):
        '''For use as a decorator.'''
        def decorated_timer_function(*args, **kwargs):
            with self:
                return fn(*args, **kwargs)
        return decorated_timer_function

    @classmethod
    def time_str(cls, secs):
        if isinstance(secs, six.string_types):
            try:
                secs = float(secs)
            except:
                return "(bad time: %s)"%secs
        sign = lambda x: x
        if secs < 0:
            secs = -secs
            sign = lambda x: ("-" + x)
        return sign("%d secs"%int(secs) if secs >= 120 else
                    "%.2f secs" % secs if secs >= 1 else
                    "%d ms" % int(secs * 1000) if secs >= .01 else
                    "%.2f ms" % (secs * 1000) if secs >= .0001 else
                    "%d ns" % int(secs * 1000 * 10000) if secs >= 1e-9 else
                    "%s" % secs)

    def log(self):
        if not self.calls:
            logging.info("<Timer %s: no calls>"%self.name)
            return
        avg = 1.0 * self.total_time / self.calls
        var = 1.0 * self.total_squared_time / self.calls - avg*avg
        std_dev = self.time_str(math.sqrt(var))
        total = self.time_str(self.total_time)
        min, max, avg = [self.time_str(t) for t in [self.min, self.max, avg]]
        logging.info("<Timer %s: N=%s, total=%s, avg=%s, min/max=%s/%s, std=%s>"
                     %(self.name, self.calls, total, avg, min, max, std_dev))

0

如果使用timeit.timeit,则命令为

timeit.timeit(function_to_test, n=10000)

引发错误 ValueError: stmt 不是字符串也不是可调用对象

或命令

timeit.timeit('function_to_test', n=10000)

引发错误 name 'function_to_test' is not defined,然后你需要: 替换 function_to_test'function_to_test'str(function_to_test),即
timeit.timeit(str(function_to_test), n=10000)

或者如果Python版本>= 3.6,另一种方式是使用f字符串,如下:

timeit.timeit(f'{function_to_test}', n=10000)

关于版本使用lambda,即timeit.timeit(lambda: function_to_test, n=10000),它可以工作,但是从我的测试来看,它需要更长的时间。
这里有一个具体的例子:
import timeit

def function_to_test(n):
    s = 1
    for i in range(n):
        s += 1
    return s
    
print("time run function_to_test: ", timeit.timeit(str(function_to_test(1000000)), number=10000))
print("time run function_to_test: ", timeit.timeit(f'{function_to_test(1000000)}', number=10000))

0

你可以使用timeit.default_timercontextmanager

from timeit import default_timer
from contextlib import contextmanager

@contextmanager
def timer():
    start_time = default_timer()
    try:
        yield
    finally:
        print("--- %s seconds ---" % (default_timer() - start_time))

使用with语句:

def looper():
    for i in range(0, 100000000):
        pass

with timer():
    looper()

输出:

--- 2.651526927947998 seconds ---

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接