使用 Python Timeit 为您的代码计时

使用 Python timeit 模块进行代码计时

本教程将指导你如何使用 Python 的 timeit 模块中的 timeit() 函数。 你将学习如何在 Python 中测量简单表达式和函数的执行时间。

对代码进行计时可以帮助你评估一段代码的运行速度,并找到需要优化的代码片段。

我们将从学习 Python 中 timeit() 函数的语法开始。 随后,我们将编写示例代码,演示如何使用它来测量 Python 模块中代码块和函数的执行时间。 让我们开始吧。

如何使用 Python 的 timeit() 函数

timeit 模块是 Python 标准库的一部分,你可以像这样导入它:

import timeit

timeit 模块中 timeit() 函数的基本语法如下:

timeit.timeit(stmt, setup, number)

这里:

  • stmt 是你要测量执行时间的代码片段。 你可以将其指定为简单的 Python 字符串、多行字符串,或者传入可调用对象的名称。
  • setup,顾名思义,表示只需要运行一次的代码片段,通常作为 stmt 执行的先决条件。 例如,如果你正在测量创建 NumPy 数组的时间,那么导入 NumPy 就是设置代码,而实际创建数组的操作才是需要计时的语句。
  • number 参数表示 stmt 要运行的次数。 number 的默认值为 100 万 (1000000),你也可以将此参数设置为你选择的任何其他值。

现在你已经了解了 timeit() 函数的语法,让我们开始编写一些示例。

测量简单 Python 表达式的执行时间

在本节中,我们将使用 timeit 来测量简单 Python 表达式的执行时间。

启动 Python REPL 并运行以下代码示例。 在这里,我们将测量求幂和整除运算运行 10000 次和 100000 次的执行时间。

请注意,我们将要计时的语句作为 Python 字符串传入,并使用分号分隔语句中的不同表达式。

>>> import timeit
>>> timeit.timeit('3**4;3//4',number=10000)
0.0004020999999738706

>>> timeit.timeit('3**4;3//4',number=100000)
0.0013780000000451764

在命令行运行 Python timeit

你也可以在命令行中使用 timeit。 这是 timeit() 函数调用的等效命令行:

$ python -m timeit -n [number] -s [setup] [stmt]
  • python -m timeit 表示我们将 timeit 作为主模块运行。
  • -n 是一个命令行选项,表示代码应运行的次数。 这相当于 timeit() 函数调用中的 number 参数。
  • 你可以使用 -s 选项定义设置代码。

在这里,我们使用等效的命令行重写前面的示例:

$ python -m timeit -n 100000 '3**4;3//4'
100000 loops, best of 5: 35.8 nsec per loop

在这个例子中,我们测量内置 len() 函数的执行时间。 字符串的初始化是使用 -s 选项传入的设置代码。

$ python -m timeit -n 100000 -s "string_1 = 'coding'" 'len(string_1)'
100000 loops, best of 5: 239 nsec per loop

在输出中,请注意我们获得了 5 次运行中最好的执行时间。 这是什么意思呢? 当你在命令行运行 timeit 时,重复选项 -r 被设置为默认值 5。这意味着指定次数的 stmt 执行将重复五次,并返回最佳执行次数。

使用 timeit 分析字符串反转方法

在处理 Python 字符串时,你可能需要反转它们。 字符串反转的两种常见方法如下:

  • 使用字符串切片
  • 使用 reversed() 函数和 join() 方法

使用字符串切片反转 Python 字符串

让我们回顾一下字符串切片的工作原理,以及如何使用它来反转 Python 字符串。 使用语法 some-string[start:stop] 返回从索引 start 开始,并延伸到索引 stop-1 的字符串片段。 让我们举一个例子。

考虑以下字符串 “Python”。 该字符串的长度为 6,索引列表为 0、1、2 到 5。

>>> string_1 = 'Python'

当你同时指定起始值和终止值时,你将获得一个从 start 延伸到 stop-1 的字符串切片。 因此,string_1[1:4] 返回 “yth”。

>>> string_1 = 'Python'
>>> string_1[1:4]
'yth'

当你未指定起始值时,将使用默认起始值零,并且切片从索引零开始并向上延伸到 stop - 1

在这里,stop 值为 3,因此切片从索引 0 开始,一直到索引 2。

>>> string_1[:3]
'Pyt'

当你没有指定终止索引时,你会发现切片从起始索引 (1) 开始并一直延伸到字符串的末尾。

>>> string_1[1:]
'ython'

忽略开始值和结束值将返回整个字符串的一个副本。

>>> string_1[::]
'Python'

让我们用步长值创建一个切片。 将开始、停止和步进值分别设置为 1、5 和 2。 我们得到一段字符串,从 1 开始延伸到 4 (不包括结束点 5),包含每隔一个字符。

>>> string_1[1:5:2]
'yh'

当你使用负步长时,你可以获得从字符串末尾开始的切片。 将步长设置为 -2,string_1[5:2:-2] 给出以下切片:

>>> string_1[5:2:-2]
'nh'

因此,为了获得字符串的反向副本,我们跳过起始值和终止值,并将步长设置为 -1,如下所示:

>>> string_1[::-1]
'nohtyP'

总结:string[::-1] 返回字符串的反向副本。

使用内置函数和字符串方法反转字符串

Python 中的内置 reversed() 函数将返回字符串元素的反向迭代器。

>>> string_1 = 'Python'
>>> reversed(string_1)
<reversed object at 0x00BEAF70>

因此,你可以使用 for 循环遍历反向迭代器:

for char in reversed(string_1):
    print(char)

并以相反的顺序访问字符串的元素。

# 输出
n
o
h
t
y
P

接下来,你可以在反向迭代器上调用 join() 方法,使用以下语法: <sep>.join(reversed(some-string))

以下代码片段展示了一些示例,其中分隔符分别是连字符和空格。

>>> '-'.join(reversed(string1))
'n-o-h-t-y-P'
>>> ' '.join(reversed(string1))
'n o h t y P'

在这里,我们不需要任何分隔符; 因此,将分隔符设置为空字符串以获取字符串的反向副本:

>>> ''.join(reversed(string1))
'nohtyP'

使用 .join(reversed(some-string)) 返回字符串的反向副本。

使用 timeit 比较执行时间

到目前为止,我们已经了解了两种反转 Python 字符串的方法。 但哪种更快呢? 让我们来找出答案。

在我们之前测量简单 Python 表达式执行时间的示例中,我们没有任何设置代码。 在这里,我们正在反转 Python 字符串。 当字符串反转操作运行由数字指定的次数时,设置代码是只运行一次的字符串的初始化。

>>> import timeit
>>> timeit.timeit(stmt="string_1[::-1]", setup = "string_1 = 'Python'", number = 100000)
0.04951830000001678
>>> timeit.timeit(stmt = "''.join(reversed(string_1))", setup = "string_1 = 'Python'", number = 100000)
0.12858760000000302

对于反转给定字符串的相同运行次数,字符串切片方法比使用 join() 方法和 reversed() 函数更快。

使用 timeit 测量 Python 函数的执行时间

在本节中,我们将学习如何使用 timeit 函数来测量 Python 函数的执行时间。 给定一个字符串列表,以下函数 hasDigit 返回至少包含一个数字的字符串列表。

def hasDigit(somelist):
     str_with_digit = []
     for string in somelist:
         check_char = [char.isdigit() for char in string]
         if any(check_char):
            str_with_digit.append(string)
     return str_with_digit

现在我们想使用 timeit 来测量这个 Python 函数 hasDigit() 的执行时间。

让我们首先确定要计时的语句 (stmt)。 它是对函数 hasDigit() 的调用,以字符串列表作为参数。 接下来,让我们定义设置代码。 你能猜出设置代码应该是什么吗?

为了成功运行函数调用,设置代码应包含以下内容:

  • 函数 hasDigit() 的定义
  • 字符串参数列表的初始化

让我们在设置字符串中定义设置代码,如下所示:

setup = """
def hasDigit(somelist):
    str_with_digit = []
    for string in somelist:
      check_char = [char.isdigit() for char in string]
      if any(check_char):
        str_with_digit.append(string)
    return str_with_digit
thislist=['puffin3','7frost','blue']
     """

接下来,我们可以使用 timeit 函数并获取 hasDigit() 函数运行 100000 次的执行时间。

import timeit
timeit.timeit('hasDigit(thislist)',setup=setup,number=100000)
# 输出
0.2810094920000097

结论

你已经学习了如何使用 Python 的 timeit 函数来测量表达式、函数和其他可调用对象的执行时间。 这可以帮助你对代码进行基准测试,比较同一函数的不同实现的执行时间,等等。

让我们回顾一下本教程中学习的内容。 你可以使用 timeit.timeit(stmt=..., setup=..., number=...) 语法调用 timeit() 函数。 另外,你也可以在命令行运行 timeit 来测量短代码片段的执行时间。

接下来,你可以探索如何使用其他 Python 分析包,如 line-profilermemprofiler,来分别分析代码的时间和内存使用情况。

此外,学习如何在 Python 中计算时间差。