如何使用Python的timeit模块捕获返回值?

40

我正在使用sklearn中的多个机器学习算法进行循环运算,并希望查看每个算法所需时间。但问题在于,我还需要返回一个值,并且不想再运行一次,因为每个算法都需要很长时间。是否有一种方法可以使用Python的timeit模块或类似的函数捕获返回值“clf”...

def RandomForest(train_input, train_output):
    clf = ensemble.RandomForestClassifier(n_estimators=10)
    clf.fit(train_input, train_output)
    return clf

当我像这样调用函数时:
t = Timer(lambda : RandomForest(trainX,trainy))
print t.timeit(number=1)

PS:我也不想设置全局的“clf”,因为我可能以后需要进行多线程或多进程操作。


2
如果你强制使用number=1,为什么还要使用timeit呢? timeit的用处在于自动处理重复计时,当你不知道应该运行函数多长时间才能得到良好的计时等情况下非常有用。在你的情况下,简单地使用time就可以了,而且你不需要任何技巧来获取返回值。 - Bakuriu
你能提供一个示例链接让我看看你所指的是什么吗?我谷歌了一下,似乎你可能在谈论的模块只涉及日期和时区等格式化。 - Leon
2
你从未听说过 time.time() 或者 time.clock() 吗?timeit 模块使用这些函数来计算时间。如果您只需要进行一次计时操作,可以直接调用它们,就像 unutbu 的回答中使用 _timer 函数一样(实际上是引用了 time.time()time.clock(),取决于操作系统)。 - Bakuriu
1
@Bakuriu 我理解 timeit 做的事情不仅仅是计时,还会关闭垃圾回收以确保我们进行公平比较。也就是说,我们要看的是执行时间,而不是墙上时间。 - Joel
10个回答

24

对于Python 3.5,您可以覆盖 timeit.template 的值。

timeit.template = """
def inner(_it, _timer{init}):
    {setup}
    _t0 = _timer()
    for _i in _it:
        retval = {stmt}
    _t1 = _timer()
    return _t1 - _t0, retval
"""

unutbu的回答适用于Python 3.4,但不适用于Python 3.5,因为在3.5中似乎已经删除了_template_func函数。


18
问题归结为timeit._template_func未返回函数的返回值:
def _template_func(setup, func):
    """Create a timer function. Used if the "statement" is a callable."""
    def inner(_it, _timer, _func=func):
        setup()
        _t0 = _timer()
        for _i in _it:
            _func()
        _t1 = _timer()
        return _t1 - _t0
    return inner

我们可以通过一些猴子补丁来控制 timeit
import timeit
import time

def _template_func(setup, func):
    """Create a timer function. Used if the "statement" is a callable."""
    def inner(_it, _timer, _func=func):
        setup()
        _t0 = _timer()
        for _i in _it:
            retval = _func()
        _t1 = _timer()
        return _t1 - _t0, retval
    return inner

timeit._template_func = _template_func

def foo():
    time.sleep(1)
    return 42

t = timeit.Timer(foo)
print(t.timeit(number=1))

返回
(1.0010340213775635, 42)

第一个值是timeit的结果(秒为单位),第二个值是函数的返回值。

注意,上面的猴子补丁仅影响在传递可调用对象时timeit.Timer的行为。如果您传递一个字符串语句,则需要(类似地)对timeit.template字符串进行猴子补丁。


嗯,这似乎返回给我的是函数本身而不是函数的返回值。但我需要用 ret_val = t.timeit(number=1)1 来捕获它以实际运行函数并将值返回给我。虽然这样做会让函数运行两次,不是吗? - Leon
1
根据您发布的代码,我不明白为什么t.timeit应该返回一个函数。当您运行我发布的代码时,是否得到与我相同的结果?如果是这样,那么您需要比较该代码和您的代码之间的区别(特别注意传递和返回的对象的类型)。 - unutbu
你是对的,我仍在使用timeit.Timer(lambda: dummy)而不是只使用timeit.Timer(dummy)。StackOverflow上有一些非常聪明的人。该死,我喜欢这个网站。 - Leon
从查看timeit的源代码来看,该模块的目的是作为命令行工具用于测试您的代码和Python本身的优化。如果您正在编写一个应用程序来测试某些东西,比如API调用的速度,最好使用time.perf_counter两次,并对这两个数字进行减法运算。 - Chris Huang-Leaver
这个可能之前是可以的,但从2023年7月11日起就不行了。所提供的代码只返回正常情况下所需的时间。Brendan Cody-Kenny提供的解决方案解决了这个问题(虽然不太美观)。 - Grismar

8
有趣的是,我也在进行机器学习,并且有类似的要求 ;-)
我通过编写一个函数来解决它,该函数: - 运行你的函数 - 打印运行时间和你的函数名称 - 返回结果
假设您想计时:
clf = RandomForest(train_input, train_output)

然后执行以下操作:
clf = time_fn( RandomForest, train_input, train_output )

标准输出将显示类似于:

mymodule.RandomForest: 0.421609s

time_fn的代码:

import time

def time_fn( fn, *args, **kwargs ):
    start = time.clock()
    results = fn( *args, **kwargs )
    end = time.clock()
    fn_name = fn.__module__ + "." + fn.__name__
    print fn_name + ": " + str(end-start) + "s"
    return results

3
如果我理解正确的话,在Python 3.5之后,您可以在每个Timer实例中定义全局变量,而无需在代码块中定义它们。我不确定它是否会与并行化具有相同的问题。
我的方法可能是这样的:
clf = ensemble.RandomForestClassifier(n_estimators=10)
myGlobals = globals()
myGlobals.update({'clf'=clf})
t = Timer(stmt='clf.fit(trainX,trainy)', globals=myGlobals)
print(t.timeit(number=1))
print(clf)

很好的解决方案,更加优雅,还可以将字典传递给 timeit.Timer。谢谢分享。 - jlandercy

3

截至2020年,在ipython或jupyter notebook中,它是

t = %timeit -n1 -r1 -o RandomForest(trainX, trainy)
t.best

你弄混了结果:OP想要计时函数clf的结果,以便不需要运行两次此函数(一次获取结果,一次获取时间),而不是IPython函数“magic” timeit的结果(其中-o确实提供)。 - mins

1
如果您不想进行猴子补丁(timeit),您可以尝试使用全局列表,如下所示。这也适用于Python 2.7,该版本中timeit()没有globals参数:
from timeit import timeit
import time

# Function to time - plaigiarised from answer above :-)
def foo():
    time.sleep(1)
    return 42

result = []
print timeit('result.append(foo())', setup='from __main__ import result, foo', number=1)
print result[0]

将会打印出时间,然后是结果。

0
你可以创建一个可调用的类,将你的函数包装起来,并捕获它的返回值,就像这样:
class CaptureReturnValue:
    def __init__(self, func):
        self.func = func
        self.return_value = None

    def __call__(self, *args, **kwargs):
        self.return_value = self.func(*args, **kwargs)

然后像这样调用timeit:
    crv = CaptureReturnValue(f1)
    elapsed_time = timeit.timeit(lambda: crv(your_parameters), number=1, globals=globals())
    print(crv.return_value)
    print(elapsed_time)

请注意,在这种情况下,由于额外的函数调用,时间开销会稍微大一些。

0
我正在使用的方法是对计时函数的结果进行“追加”运行时间。因此,我使用“time”模块编写了一个非常简单的装饰器:
def timed(func):
    def func_wrapper(*args, **kwargs):
        import time
        s = time.clock()
        result = func(*args, **kwargs)
        e = time.clock()
        return result + (e-s,)
    return func_wrapper

然后我使用修饰器来计时所需的函数。


0
原始问题需要允许多个结果、多线程和多进程。对于所有这些,队列是一个很好的选择。
# put the result to the queue inside the function, via globally named qname
def RandomForest(train_input, train_output):
    clf = ensemble.RandomForestClassifier(n_estimators=10)
    clf.fit(train_input, train_output)
    global resultq
    resultq.put(clf)
    return clf

# put the result to the queue inside the function, to a queue parameter
def RandomForest(train_input, train_output,resultq):
    clf = ensemble.RandomForestClassifier(n_estimators=10)
    clf.fit(train_input, train_output)
    resultq.put(clf)
    return clf

# put the result to the queue outside the function
def RandomForest(train_input, train_output):
    clf = ensemble.RandomForestClassifier(n_estimators=10)
    clf.fit(train_input, train_output)
    return clf


#usage:
#     global resultq
#     t=RandomForest(train_input, train_output)
#     resultq.put(t)

# in a timeit usage, add an import for the resultq into the setup.
setup="""
from __main__ import resultq
"""

# # in __main__  # #

#  for multiprocessing and/or mulithreading
import multiprocessing as mp
global resultq=mp.Queue() # The global keyword is unnecessary if in __main__ ' Doesn't hurt

# Alternatively, 

# for multithreading only
import queue
global resultq=queue.Queue() # The global keyword is unnecessary if in __main__ ' Doesn't hurt

#   do processing

# eventually, drain the queue

while not resultq.empty():
  aclf=resultq.get()
  print(aclf)

-1

对于 Python 3.X,我使用以下方法:

# Redefining default Timer template to make 'timeit' return
#     test's execution timing and the function return value
new_template = """
def inner(_it, _timer{init}):
    {setup}
    _t0 = _timer()
    for _i in _it:
        ret_val = {stmt}
    _t1 = _timer()
    return _t1 - _t0, ret_val
"""
timeit.template = new_template

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