首页 > 解决方案 > 为什么 timeit() 函数在传递函数和字符串表达式时返回不同的结果?

问题描述

我正在我的 Python REPL 中尝试 pythontimeit函数。它可以通过两种方式对小段代码进行计时:作为可调用的或作为带引号的表达式。我想知道为什么下面的代码会产生不同的计时结果。

>>> import timeit
>>> timeit.timeit("lambda *args: None")
0.058281898498535156
>>> timeit.timeit(lambda *args: None)
0.0947730541229248
>>>

我的直觉告诉我应该有更多与引用的字符串变体相关的“开销”,因为它需要解释,但情况似乎并非如此。但显然我的直觉是错误的..

这是另一个代码片段。调用可调用函数与计时引用的函数语句之间似乎没有很大的时间差异:

>>> def costly_func():
...     return list(map(lambda x: x^2, range(10)))
... 
>>> import timeit
>>> timeit.timeit(costly_func)
2.421797037124634
>>> timeit.timeit("list(map(lambda x: x^2, range(10)))")
2.3588619232177734

标签: pythontimeit

解决方案


观察:

>>> def costly():
...  return list(map(str, list(range(1_000_000))))
...
>>> timeit.timeit(costly, number=100)
30.65105245400082
>>> timeit.timeit('costly', number=1_000_000_000, globals=globals())
27.45540758000061

number论据。执行 costly100 次函数需要 30 秒。执行表达式 costly1'000'000'000 (!) 次花费了将近 30 秒。

为什么?因为第二个代码没有执行函数costly!它唯一执行的是表达式costly:注意缺少括号,这意味着它不是函数调用。该表达式costly基本上是一个无操作(好吧,它只需要检查名称“costly”是否存在于当前范围内,仅此而已),这就是它如此之快的原因,如果 Python 足够聪明,可以优化它,执行表达式costly不是 costly()!)将是瞬时的!

在您的情况下,说lambda *args: None只是定义一个匿名函数,对吗?当您执行此确切代码时,会创建一个新函数,但不会执行(为此,您应该调用它:)(lambda *args: None)()

因此,对字符串 "lambda *args: None"进行计时timeit.timeit("lambda *args: None")基本上可以测试 Python 吐出新匿名函数的速度。

通过测试 Python执行现有函数的速度来为函数本身计时。timeit.timeit(lambda *args: None)

吐出新创建的函数是小菜一碟,而实际运行它们可能非常困难。

以这段代码为例:

def Ackermann(m, n):
    if m == 0:
        return n + 1
    if m > 0:
        if n == 0:
            return Ackermann(m - 1, 1)
        elif n > 0:
            return Ackermann(m - 1, Ackermann(m, n - 1))

如果你把那个确切的代码放在一个字符串timeit中,你会得到这样的:

>>> code = """def Ackermann(m, n):
...     if m == 0:
...         return 0
...     if m > 0:
...         if n == 0:
...             return Ackermann(m - 1, 1)
...         elif n > 0:
...             return Ackermann(m - 1, Ackermann(m, n - 1))"""
>>> timeit.timeit(code, number=1_000_000)
0.10481472999890684

现在尝试timeit函数本身:

>>> timeit.timeit(lambda : Ackermann(6, 4), number=1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/timeit.py", line 232, in timeit
    return Timer(stmt, setup, timer, globals).timeit(number)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/timeit.py", line 176, in timeit
    timing = self.inner(it, self.timer)
  File "<timeit-src>", line 6, in inner
  File "<stdin>", line 1, in <lambda>
  File "<stdin>", line 8, in Ackermann
  File "<stdin>", line 8, in Ackermann
  File "<stdin>", line 8, in Ackermann
  [Previous line repeated 1 more time]
  File "<stdin>", line 6, in Ackermann
  File "<stdin>", line 8, in Ackermann
  File "<stdin>", line 6, in Ackermann
  File "<stdin>", line 8, in Ackermann
  File "<stdin>", line 8, in Ackermann
  File "<stdin>", line 8, in Ackermann
  [Previous line repeated 983 more times]
  File "<stdin>", line 6, in Ackermann
  File "<stdin>", line 2, in Ackermann
RecursionError: maximum recursion depth exceeded in comparison

看 - 你甚至不能运行它!实际上,可能没有人可以,因为它是如此的递归!

但是,为什么第一次调用成功了?因为它没有执行任何东西,它只是吐出了很多新功能,并在不久之后将它们全部删除。


推荐阅读