timeit ValueError: stmt is neither a string nor callable

timeit ValueError: stmt is neither a string nor callable

我在 Python 玩过 timeit,遇到了一个奇怪的问题。

我定义了一个简单的函数add。当我传递 add 两个字符串参数时,timeit 起作用。但是当我传递 add 两个 int 参数时它会引发 ValueError: stmt is neither a string nor callable

>>> import timeit
>>> def add(x,y):
...     return x + y
... 


>>> a = '1'
>>> b = '2'
>>> timeit.timeit(add(a,b))
0.01355926995165646


>>> a = 1
>>> b = 2
>>> timeit.timeit(add(a,b))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/anaconda/lib/python3.6/timeit.py", line 233, in timeit
    return Timer(stmt, setup, timer, globals).timeit(number)
  File "/anaconda/lib/python3.6/timeit.py", line 130, in __init__
    raise ValueError("stmt is neither a string nor callable")
ValueError: stmt is neither a string nor callable

为什么参数类型在这里很重要?

my question is why the parameter type matters here?

函数参数在函数被调用之前被完全评估。这意味着当你这样做时:

timeit.timeit(add(a,b))

那么在使用timeit之前已经计算了add(a,b)。所以,它没有时间。

当 a 和 b 是数字字符串时 timeit.timeit(add(a,b)) "works" 的原因只是一个愚蠢的原因:它正在计算 '12' 的时间。这里调用add('1', '2')的结果恰好是一个有效的Python代码串。 timeit 编译它并假定您想要计算文字整数 12 的计算时间。

您的错误是假设 Python 将表达式 add(a, b) 传递给 timeit()。事实并非如此,add(a, b) 不是字符串,它是一个表达式,所以 Python 而不是 执行 add(a, b) 结果该调用的 被传递给 timeit() 调用。

所以对于 add('1', '2'),结果是 '12',一个字符串。将字符串传递给 timeit() 没问题。但是 add(1, 2)3,一个整数。 timeit(3) 给你一个例外。当然,计时 '12' 并不是那么有趣,但这是一个产生整数值 12:

的有效 Python 表达式
>>> import timeit
>>> def add(x, y):
...     return x + y
...
>>> a = '1'
>>> b = '2'
>>> add(a, b)
'12'
>>> timeit.timeit('12')
0.009553937998134643
>>> a = 1
>>> b = 2
>>> add(a, b)
3
>>> timeit.timeit(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/.../lib/python3.7/timeit.py", line 232, in timeit
    return Timer(stmt, setup, timer, globals).timeit(number)
  File "/.../lib/python3.7/timeit.py", line 128, in __init__
    raise ValueError("stmt is neither a string nor callable")
ValueError: stmt is neither a string nor callable

这很正常;否则,你怎么能将一个函数的结果直接传递给另一个函数呢? timeit.timeit() 另一个 Python 函数 ,没什么特别的,它会禁用表达式的正常计算。

您想要的是将带有表达式 的字符串传递给timeit()timeit() 无权访问您的 add() 函数,或 ab,因此您需要使用第二个参数(设置字符串)为其提供访问权限。您可以使用 from __main__ import add, a, b 导入 add 函数对象:

timeit.timeit('add(a, b)', 'from __main__ import add, a, b')

现在您可以获得更有意义的结果:

>>> import timeit
>>> def add(x, y):
...     return x + y
...
>>> a = '1'
>>> b = '2'
>>> timeit.timeit('add(a, b)', 'from __main__ import add, a, b')
0.16069997000158764
>>> a = 1
>>> b = 2
>>> timeit.timeit('add(a, b)', 'from __main__ import add, a, b')
0.10841095799696632

所以添加整数比添加字符串更快。您可能想尝试使用不同大小的整数和字符串,但添加整数仍将是更快的结果。

解决类型 "problem" 的另一种方法是将函数的结果作为字符串传递!

timeit.timeit('%s'%(add(1,2)))
or
timeit.timeit(f'{add(1,2)}')

使用字符串版本添加 returns 一个字符串,它可以对其进行评估。所以“12”是一个有效的 python 表达式,而 3 不是。

timeit.timeit("12") # works
timeit.timeit(3) # does not

使用 timeit 的最佳方法是用 lambda 包装要测试的函数:

timeit.timeit(lambda: add(1,2))

这比处理字符串更优雅,更不容易出错。

请注意,lambda 会给每次调用带来一些轻微的开销。如果您的函数做任何远程复杂的事情,这将可以忽略不计,但对于非常简单的片段(如“a+b”),开销将产生重大影响。

我知道这是一个很老的话题,但我发现使用 functools 的类似方法,当您想检查单个函数并传递其参数而不使用字符串或 lambda 时:

import timeit
import functools

def add(x,y):
    return x + y

a = 1
b = 2

t = timeit.Timer(functools.partial(add, a, b))
print(t.timeit(10))

输出:

4.565001290757209e-06

对我有用,太棒了! 之前用过datetime,不过这样舒服多了!