【问题标题】:Python time measure functionPython时间测量函数
【发布时间】:2011-07-25 14:26:38
【问题描述】:

我想创建一个 python 函数来测试每个函数花费的时间并打印它的名字和它的时间,我如何打印函数名,如果有其他方法,请告诉我

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

【问题讨论】:

标签: python time callback


【解决方案1】:

首先,我强烈建议使用profiler 或至少使用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__ 获取。

【讨论】:

  • 正是我想要的:) ...但是你们说服我使用 python 分析器
  • 看起来像这样假设 time.time() 自纪元以来以微秒为单位报告时间?文档说它以秒为单位报告时间docs.python.org/2/library/time.html#time.time
  • 这个不能生效,在func中使用yield后。我怎样才能仍然使用这种方法并且可以使用yield?
  • def timing(f): def wrap(*args, **kwargs): time1 = time.time() ret = f(*args, **kwargs) time2 = time.time() print '%s 函数耗时 %0.3f ms' % (f.func_name, (time2-time1)*1000) return ret return wrap
  • 自己写有什么坏处?存储经过时间的列表并检查它们的分布是否足够简单?
【解决方案2】:

玩了timeit模块后,我不喜欢它的界面,和下面两种方法相比,没有那么优雅。

以下代码使用 Python 3。

装饰器方法

这与@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 = ... 线路计时。如果将其更改为函数,它将是:

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

看起来不太好...如果你在 Python 2 中,它没有 nonlocal 关键字。

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

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

【讨论】:

  • 有趣的贡献,但是我发现在你提到的装饰器方法中,你必须更改timeit接口并使用functools模块的wraps()函数。我的意思是不需要所有额外的代码。
  • 需要import functools
  • 请注意,您的装饰器会丢失原始函数的返回值
  • 我喜欢使用上下文进行计时的想法。我想知道这是否可以扩展为重复定义数量的时间。我试过这个,但它不起作用:`@contextmanager def timeit_context(name, repeats=1): startTime = time.time() for ind in range(repeats): yield elapsedTime = time.time() - startTime print( '[{}] 在 {} 毫秒内完成'.format(name, elapsedTime * 1000./repeats)) `
【解决方案3】:

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

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

也可以向函数发送参数。你所需要的只是使用装饰器来包装你的函数。更多解释在这里:http://www.pythoncentral.io/time-a-python-function/

您可能对编写自己的时序语句感兴趣的唯一情况是,如果您只想运行一个函数并且还想获得它的返回值。

使用timeit 模块的优势在于它可以让您repeat 执行次数。这可能是必要的,因为其他进程可能会干扰您的计时准确性。因此,您应该多次运行它并查看最低值。

【讨论】:

  • 使用包装器和装饰器向函数发送参数?为什么不timeit.timeit(lambda: func(a,b,c), number=1)?我在终端中对假设解决方案进行测试时使用它。
【解决方案4】:

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 依赖项,而谁想要制作一个大的旧字符串来做到这一点?这种解决方案更简单、更灵活。
  • 我认为这个解决方案更接近原帖的精神,或者至少是我的阅读方式。所有其他人都有很多样板代码,而很明显,在这里我们正在讨论如何快速通过进行内联更改或尽可能接近来快速获取我的代码的单行代码可以内联。
【解决方案5】:

使用装饰器 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

查看我博客上的帖子:

post on mobilepro.pl Blog

my post on Google Plus

【讨论】:

    【解决方案6】:

    有一个简单的计时工具。 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
    

    希望对你有帮助

    【讨论】:

      【解决方案7】:

      我的做法:

      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)

      你得到了答案。

      【讨论】:

        【解决方案8】:

        详细阐述@Jonathan Ray,我认为这会做得更好

        import time
        import inspect
        
        def timed(f:callable):
            start = time.time()
            ret = f()
            elapsed = 1000*(time.time() - start)
            source_code=inspect.getsource(f).strip('\n') 
            logger.info(source_code+":  "+str(elapsed)+" seconds")
            return ret
        

        它允许使用常规的代码行,比如a = np.sin(np.pi) 并将其简单地转换为

        a = timed(lambda: np.sin(np.pi))
        

        以便将时间打印到记录器上,并且您可以将相同的结果分配给您可能需要进一步工作的变量。

        我想在 Python 3.8 中可以使用 := 但我还没有 3.8

        【讨论】:

        • time.time() 已经返回秒数,因此您不需要在elapsed = 1000*(time.time() - start) 行中乘以 1000。否则,非常好。谢谢!
        【解决方案9】:

        下面是一个 Timer 类:

        • 易于使用:直接使用或作为装饰函数,
        • 测量很多:总呼叫、总时间、平均时间和标准。偏差。
        • 打印漂亮的时间
        • 线程安全

        这就是你如何使用它:

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

        【讨论】:

          【解决方案10】:

          您可以将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 ---
          

          【讨论】:

            【解决方案11】:

            这是一个通用的解决方案

            def timed(fn):
                # make sure wherever u used this, imports will be ready
                from time import perf_counter
                from functools import wraps
                # wraps preserves the metadata of fn
                @wraps(fn)
                def inner(*args, **kwargs):
                    start = perf_counter()
                    result = fn(*args, **kwargs)
                    end = perf_counter()
                    elapsed = end - start
                    args_ = [str(a) for a in args]
                    kwargs_ = ["{0}={1}".format(k, v) for (k, v) in kwargs.items()]
                    all_args = args_ + kwargs_
                    args_str = ",".join(all_args)
                    print("{0} ({1}) took {2:.6f} to run.".format(fn.__name__, args_str, elapsed))
                    return result
                return inner
            

            定义一个函数:

            @timed
            def sum_up(a,b):
                 return a+b
            

            现在叫它:

            sum_up(2,9)
            

            【讨论】:

              【解决方案12】:

              对于使用timeit.timeit的情况,if命令

              timeit.timeit(function_to_test, n=10000)
              

              引发错误ValueError: stmt is neither a string nor callable

              或命令

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

              【讨论】:

                猜你喜欢
                • 2010-12-13
                • 2014-11-15
                • 2010-12-30
                • 1970-01-01
                • 1970-01-01
                • 2021-06-23
                • 1970-01-01
                • 2021-12-28
                • 1970-01-01
                相关资源
                最近更新 更多