为什么在Python中“not”比“bool()”快(或Python函数与语句的速度)?

11 投票
2 回答
2341 浏览
提问于 2025-04-19 02:32

前几天我发现了一个有趣的现象。我在尝试不同的方法来判断一个对象的“真假值”(也就是它是否被认为是“真”),并测试每种方法的速度时,发现使用 not 要比使用 bool 快得多。

>>> bool([5, 6, 7])
True
>>> bool([])
False
>>> not not [5, 6, 7]
True
>>> not not []
False
>>> import timeit
>>> from numpy import mean
>>> mean(timeit.repeat('bool(a)', 'a = [5, 6, 7]', repeat=10))
0.19072036743164061
>>> mean(timeit.repeat('bool(a)', 'a = []', repeat=10))
0.18562331199645996
>>> mean(timeit.repeat('not not a', 'a = [5, 6, 7]', repeat=10))
0.072056698799133304
>>> mean(timeit.repeat('not not a', 'a = []', repeat=10))
0.073475956916809082
>>> mean(timeit.repeat('not a', 'a = [5, 6, 7]', repeat=10))
0.043941426277160647
>>> mean(timeit.repeat('not a', 'a = []', repeat=10))
0.044287109375000001

我们可以看到,使用 bool 函数的速度明显比使用 not 语句慢,尽管它们最终做的事情是一样的(返回对象的布尔状态)。大家都知道在 Python 中,调用函数的开销是比较大的,但我没想到在这种情况下会有这么大的差异,原因有以下几点:

  • bool() 是一个内置函数,这意味着它是用 C 语言写的,我本以为它的开销应该比较小。
  • 在这两种情况下,Python 都需要在内部判断对象的“真假值”(我想它们在内部使用的是相同的 C 例程来完成这个判断)。
    • 实际上,not 需要返回对象“真假值”的逻辑相反值,所以理论上它的工作量要稍微多一些(但也许有某种实现细节可以绕过这个问题)。

在我看来,因为这两个函数基本上做的是同样的事情,所以所有额外的时间一定是来自于函数调用的开销。如果真是这样,那为什么一个语句能比一个函数避免这么多的开销呢?如果不是开销,那为什么 bool() 会比 not 慢这么多呢?


更新:这里还有最小时间的数据,除了平均时间。

>>> min(timeit.repeat('bool(a)', 'a = [5, 6, 7]', repeat=10))
0.18180489540100098
>>> min(timeit.repeat('bool(a)', 'a = []', repeat=10))
0.1821761131286621
>>> min(timeit.repeat('not not a', 'a = [5, 6, 7]', repeat=10))
0.0707249641418457
>>> min(timeit.repeat('not not a', 'a = []', repeat=10))
0.07100605964660645
>>> min(timeit.repeat('not a', 'a = [5, 6, 7]', repeat=10))
0.04264092445373535
>>> min(timeit.repeat('not a', 'a = []', repeat=10))
0.04357004165649414

2 个回答

2

所有的函数调用都会有一定的开销——因为你需要创建一个新的栈帧来存放在这个函数里用到的新变量。

如果这个操作比较复杂或者耗时,那些开销就不那么明显了。但因为你现在讨论的是一个非常简单的操作,所以这些开销就显得特别突出。

5

(这不是答案,只是一些文档说明):下面是给定表达式的字节码序列:

bool(a):

1           0 LOAD_NAME                0 (bool)
            3 LOAD_NAME                1 (a)
            6 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
            9 RETURN_VALUE

not a:

1           0 LOAD_NAME                0 (a)
            3 UNARY_NOT
            4 RETURN_VALUE

not not a:

1           0 LOAD_NAME                0 (a)
            3 UNARY_NOT
            4 UNARY_NOT
            5 RETURN_VALUE

撰写回答