Python 时间度量函数

我想创建一个 python 函数来测试每个函数花费的时间,并打印它的名称与它的时间,我如何可以打印的函数名称,如果有其他方法这样做,请告诉我

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

首先,我强烈建议使用 侧写师或者至少使用 计时

但是,如果您想要严格地编写自己的计时方法来学习,那么可以从这里开始使用装饰器。

巨蟒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

其用法非常简单,只需使用@timer 装饰器:

@timing
def do_work():
#code

巨蟒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以获得字符串形式的函数名(在 Python2中) ,或者在 Python3中获得 f.__name__

在使用了 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模块有什么问题。这可能是最简单的方法。

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

也可以向函数发送参数。所有您需要的就是使用修饰符将函数包装起来。更多解释: http://www.pythoncentral.io/time-a-python-function/

您可能有兴趣编写自己的计时语句的唯一情况是,您只想运行一次函数,并且还想获得它的返回值。

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

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)

使用 Decorator Python 库的 Decorator 方法:

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 + 上的帖子

有一个简单的计时工具

它可以像 装潢师一样工作:

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

希望能有所帮助

我的做法是:

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)

你得到了答案。

详述“ 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

下面是一个 Timer 类:

  • 使用方便: 直接使用或作为装饰功能,< 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))

你可以同时使用 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 ---

这里有一个通用的解决方案

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)

enter image description here

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

timeit.timeit(function_to_test, n=10000)

提高误差

或者命令

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

提高错误 name 'function_to_test' is not defined,然后你需要:

str(function_to_test)代替 function_to_test'function_to_test',即

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

或者如果 Python version > = 3.6,另一种方法是使用 f string 作为

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