【问题标题】:timeit ValueError: stmt is neither a string nor callabletimeit ValueError:stmt 既不是字符串也不是可调用的
【发布时间】:2019-06-05 17:47:20
【问题描述】:

我在 Python 中使用timeit,遇到了一个奇怪的问题。

我定义了一个简单的函数addtimeit 在我传递 add 两个字符串参数时起作用。但是当我传递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

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

【问题讨论】:

  • 为什么对象的类型不重要?

标签: python callable timeit


【解决方案1】:

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

所以对于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

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

【讨论】:

    【解决方案2】:

    使用字符串版本 add 返回一个字符串,timeit 可以评估该字符串。所以“12”是一个有效的python表达式,而3不是。

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

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

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

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

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

    【讨论】:

    • 我已经测试了您的版本并将其与带有字符串的版本进行了比较,并且使用 lambda 会增加一些开销。与常规字符串版本相比,它花费了近 50% 的时间。:timeit.timeit(stmt='add(2,3)', setup='from main import add') vs timeit.timeit (lambda: add(2, 3))
    • 为了测试一个方法调用,很容易增加大量时间。我的印象是你应该通过多次调用一个方法来对其进行基准测试。这将使增加的时间可以忽略不计。
    • 一个函数不能比这简单得多 - 只要您正在测试的代码是一个普通函数,就可以忽略一个额外的方法调用。我会将此信息添加到答案中
    【解决方案3】:

    我的问题是为什么参数类型在这里很重要?

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

    timeit.timeit(add(a,b))
    

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

    timeit.timeit(add(a,b)) 在 a 和 b 是数字字符串时“起作用”的原因只是一个愚蠢的原因:它正在计时 '12' 的评估。调用add('1', '2') 的结果恰好是此处的有效 Python 代码字符串。 timeit 编译它并假设您想要计算文字整数 12 的计算时间。

    【讨论】:

    • 感谢您的慷慨回答!是否意味着传递 add('1', '2') 相当于下面的代码? timeit.timeit("add(1,2)","from main import add")
    • OP应该如何修改代码,以便计算计算整数和的时间?
    • @liangli 不完全是。但这是使用整数参数为add 的实际执行计时的一种正确方法。
    • @liangli:不,是add()返回值传递给timeitadd('1', '2') 返回字符串'12',所以它等价于timeit('12')
    • 由于 add(a,b) 传递的不是字符串,我很好奇幕后发生了什么。说,为什么timeit不把add(1,2)当作有效的python代码字符串?
    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

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

      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
      

      【讨论】:

        猜你喜欢
        • 2013-05-13
        • 2012-11-10
        • 1970-01-01
        • 1970-01-01
        • 2023-03-11
        • 2013-12-20
        • 1970-01-01
        • 1970-01-01
        • 2019-05-13
        相关资源
        最近更新 更多