如何在使用timeit.Timer()时传递函数参数

75
这是一个简单程序的大纲。
# some pre-defined constants
A = 1
B = 2

# function that does something critical
def foo(num1, num2):
    # do something

# main program.... do something to A and B
for i in range(20):
    # do something to A and B
    # and update A and B during each iteration

import timeit
t = timeit.Timer(stmt="foo(num1,num2)")  
print t.timeit(5)

我一直在收到"global name foo is not defined"的错误提示......谁能帮忙解决一下吗?谢谢!

首先,这里的缩进很令人困惑。看起来 foo 被定义在不同于 t 的作用域中... - senderle
嗨。我修正了缩进。现在看起来更好了吗?:] 谢谢。 - CppLearner
timeit 对于一行代码是可以的,但对于其他情况最好检查这个方法:https://dev59.com/OW035IYBdhLWcg3wVul1 - vincent
在Python3中,这个问题有一个非常简单的解决方案,您只需在timeit调用中添加“globals = globals()”参数即可在全局命名空间中运行表达式:https://dev59.com/lm445IYBdhLWcg3wAFm0#51913199 - RustyToms
简而言之:duration,return_value= timeit.Timer(functools.partial(your_function_name, argument_1, argument_2,argument_3)).timeit(1),其中 your_function_namefoo,如果你编写了一个函数如下:def foo(argument_1, argument_2, argument_3):。同时包括 @Hugh Bothwell 的 timeit.template = """.. 块。return_value 是函数返回的值,例如 argument_1 + argument_2。使用 import timeit, functools。此解决方案由 @Jose Ricardo Bustos M. 提供。 - a.t.
12个回答

95

如果使用闭包创建参数,timeit函数可以使用这些参数,我们可以通过将它们包装在另一个函数中来添加这些行为。

def foo(num1, num2):
    def _foo():
        # do something to num1 and num2
        pass
    return _foo

A = 1
B = 2

import timeit
t = timeit.Timer(foo(A,B))  
print(t.timeit(5))

更简短的写法是,我们可以使用functools.partial代替显式的闭包声明

def foo(num1, num2):
    # do something to num1 and num2
    pass

A = 1
B = 2

import timeit, functools
t = timeit.Timer(functools.partial(foo, A, B)) 
print(t.timeit(5))

使用lambda进行编辑,感谢@jupiterbjy

我们可以使用没有参数的lambda函数来代替functools库。

def foo(num1, num2):
    # do something to num1 and num2
    pass

A = 1
B = 2

import timeit
t = timeit.Timer(lambda: foo(A, B)) 
print (t.timeit(5))

3
谢谢,functools帮助我避免了使用全局变量、糟糕的字符串方法,以及装饰器/闭包的技巧。 - jatal
1
@Peter.k 这是因为如果你想要使用 timeit.timeit(),它需要将你的函数作为字符串传递进去,例如 timeit.timeit('foo') - 13ros27
3
谢谢,这正是我所寻找的!还应该注意的是,您甚至不需要实例化Timer,这也可以工作:timeit.timeit(functools.partial(foo, A, B), number=5) - typeracer
2
对于当前的CPython3.8,相比于functools.partial(foo, A, B),调用lambda: foo(A, B)是否不太好用于timeit - jupiterbjy
1
@jupiterbjy 你说的对,你可以使用 lambda 函数代替 functools 库。 - Jose Ricardo Bustos M.
显示剩余3条评论

19
代码片段必须是自包含的,不能进行外部引用。您必须在语句字符串或设置字符串中定义您的值:
import timeit

setup = """
A = 1
B = 2

def foo(num1, num2):
    pass

def mainprog():
    global A,B
    for i in range(20):
        # do something to A and B
        foo(A, B)
"""

t = timeit.Timer(stmt="mainprog()" setup=setup)
print(t.timeit(5))

更好的方法是,重写你的代码,不要使用全局变量。

7
Lucas S.的回答让人感觉有些不对,至少很尴尬。 - Arnaud P

16
假设你的模块文件名为test.py。
# some pre-defined constants
A = 1
B = 2

# function that does something critical
def foo(n, m):
    pass

# main program.... do something to A and B
for i in range(20):
    pass

import timeit
t = timeit.Timer(stmt="test.foo(test.A, test.B)", setup="import test")  
print t.timeit(5)

12

我通常会创建一个额外的函数:

def f(x,y):
    return x*y

v1 = 10
v2 = 20

def f_test():
    f(v1,v2)

print(timeit.timeit("f_test()", setup="from __main__ import f_test"))

1
仅在 v1v2 是全局变量时才起作用。如果它们是函数 f 的局部变量,则不起作用。 - Kyrol

9

您的函数需要在设置字符串中定义。一个好的方法是通过设置模块来设置您的代码,这样您只需执行

t = timeit.Timer("foo(num1, num2)", "from myfile import foo")
t.timeit(5)

否则,您将不得不在设置语句内将所有设置定义为字符串。
setup = """
 # some pre-defined constants
A = 1
B = 2

# function that does something critical
def foo(num1, num2):
    # do something

# main program.... do something to A and B
for i in range(20):
    # do something to A and B
    # and update A and B during each iteration
"""

t = timeit.Timer("foo(num1, num2)", setup)
t.timeit(5)

我刚刚发现的一件很棒的事情是,iPython有一个使用cProfile的快捷方式。

def foo(x, y):
    print x*y

%prun foo("foo", 100)

6

有一个更简单的解决方案(至少适用于Python 3),你可以使代码在当前全局命名空间中执行:

t = timeit.Timer(stmt="foo(num1,num2)", globals=globals())

我知道全局变量不是首选,但如果你只是想快速编写脚本来检查某些内容,我认为这是最容易实现的方法。

https://docs.python.org/3/library/timeit.html#examples


只需小心重复,因为结果将被缓存到全局变量中。 - arivero

6

另一种选择是通过functools将函数绑定到其参数(类似于std::bind)。然后,您无需向 timeit 传递参数,由 functool.partial 返回的可调用对象会处理此事:

    def findMax(n):#n is an array
        m = 0
        c = 0
        for i in range(len(n)):
            c += 1
            if m < n[i]:
                m = n[i]
        return m, c


import timeit
import functools
a = [6, 2, 9, 3, 7, 4, 5]
t = timeit.Timer(functools.partial(findMax,a))
t.timeit(100)

对我来说,这是迄今为止最简单的解决方案。它还适用于类函数 =)有什么缺点/副作用吗? - stfn

3
这是一个如何将定时例程分隔开的示例,而不需要调用全局变量。
def foo(a, b):
    '''Do something to `a` and `b`'''
    return a + b

def time_foo():
    '''Create timer object simply without using global variables'''
    import timeit

    _foo = foo
    a = 1
    b = 2

    # Get `Timer` oject, alternatively just get time with `timeit.timeit()`
    t = timeit.Timer('_foo(a, b)', globals=locals())

    return t

如果您想使用相同的 timeit 函数计时其他函数,甚至可以进行泛化。以下是使用您的示例 main()例程的示例:

def foo1(a, b):
    '''Add `a` and `b`'''
    return a + b

def foo2(a, b):
    '''More math on `a` and `b`'''
    return (a**2 * b)**2

def time_foo(func, **kwargs):
    '''Create timer object simply without using global variables'''
    import timeit
    return timeit.timeit('func(**kwargs)', globals=locals())

def run():
    '''Modify inputs to foo and see affect on execution time'''

    a = 1
    b = 2
    for i in range(10):
        # Update `a` and `b`
        a += 1
        b += 2
        # Pass args to foo as **kwargs dict
        print('foo1 time: ', time_foo(foo1, **{'a':a, 'b':b}))
        print('foo2 time: ', time_foo(foo2, **{'a':a, 'b':b}))

    return None

1
这应该是有效的:

import timeit

def f(x,y):
    return x*y

x = 5
y = 7

print(timeit.timeit(stmt='f(x,y)',
                    setup='from __main__ import f, x, y',
                    number=1000))

这可能是最简单/最短的方法,连同lambda方法一起使用。 - AndroidX

0

我更喜欢创建一个 static 类,其中包含所有数据,在运行计时器之前就准备好了。

另外需要注意的是,最好在函数中进行测试运行,而不是在全局空间中进行,因为全局空间没有利用 FAST_LOAD 的优势。为什么 Python 代码在函数中运行更快?

class Data(object):
    """Data Creation"""
    x = [i for i in range(0, 10000)]
    y = tuple([i for i in range(0, 10000)])
    def __init__(self):
        pass

import timeit

def testIterator(x):
    for i in range(10000):
        z = i


print timeit.timeit("testIterator(Data.x)", setup="from __main__ import testIterator, Data", number=50)
print timeit.timeit("testIterator(Data.y)", setup="from __main__ import testIterator, Data", number=50)

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