while (1) 与 while(True) 的区别是什么(在Python 2字节码中)?

124 投票
3 回答
108517 浏览
提问于 2025-04-16 04:42

我对一个关于 Perl 中无限循环的问题很感兴趣:while (1) 和 for (;;) 之间有速度差异吗?,于是决定在 Python 中做个类似的比较。我原本以为在 while(True): passwhile(1): pass 这两种写法中,编译器会生成相同的字节码,但实际上在 Python 2.7 中并不是这样。

下面这个脚本:

import dis

def while_one():
    while 1:
        pass

def while_true():
    while True:
        pass

print("while 1")
print("----------------------------")
dis.dis(while_one)

print("while True")
print("----------------------------")
dis.dis(while_true)

产生了以下结果:

while 1
----------------------------
  4           0 SETUP_LOOP               3 (to 6)

  5     >>    3 JUMP_ABSOLUTE            3
        >>    6 LOAD_CONST               0 (None)
              9 RETURN_VALUE        
while True
----------------------------
  8           0 SETUP_LOOP              12 (to 15)
        >>    3 LOAD_GLOBAL              0 (True)
              6 JUMP_IF_FALSE            4 (to 13)
              9 POP_TOP             

  9          10 JUMP_ABSOLUTE            3
        >>   13 POP_TOP             
             14 POP_BLOCK           
        >>   15 LOAD_CONST               0 (None)
             18 RETURN_VALUE        

使用 while True 的写法明显更复杂。这是为什么呢?

在其他情况下,Python 似乎把 True 当作 1 来处理:

>>> True == 1
True

>>> True + True
2

那为什么 while 会把两者区分开呢?

我注意到在 Python 3 中,确实是用相同的操作来评估这两个语句:

while 1
----------------------------
  4           0 SETUP_LOOP               3 (to 6) 

  5     >>    3 JUMP_ABSOLUTE            3 
        >>    6 LOAD_CONST               0 (None) 
              9 RETURN_VALUE         
while True
----------------------------
  8           0 SETUP_LOOP               3 (to 6) 

  9     >>    3 JUMP_ABSOLUTE            3 
        >>    6 LOAD_CONST               0 (None) 
              9 RETURN_VALUE         

那么在 Python 3 中,布尔值的评估方式有什么变化吗?

3 个回答

7

这是一个七年前的问题,已经有了很好的回答,但问题中有一个误解没有在任何回答中提到,这可能会让一些被标记为重复的问题感到困惑。

在其他情况下,Python表现得好像 True 等于 1:

>>> True == 1
True

>>> True + True
2

那为什么 while 会区分这两者呢?

实际上,while 在这里并没有做任何不同的事情。它区分 1True 的方式和 + 的例子是完全一样的。


这是 2.7 版本:

>>> dis.dis('True == 1')
  1           0 LOAD_GLOBAL              0 (True)
              3 LOAD_CONST               1 (1)
              6 COMPARE_OP               2 (==)
              9 RETURN_VALUE

>>> dis.dis('True == 1')
  1           0 LOAD_GLOBAL              0 (True)
              3 LOAD_GLOBAL              0 (True)
              6 BINARY_ADD
              9 RETURN_VALUE

现在比较一下:

>>> dis.dis('1 + 1')
  1           0 LOAD_CONST               1 (2)
              3 RETURN_VALUE

它为每个 True 发出了一个 LOAD_GLOBAL (True),而优化器对全局变量无能为力。所以,while 区分 1True 的原因和 + 是完全一样的。(而 == 不区分它们,因为优化器不会优化掉比较操作。)


现在比较 3.6 版本:

>>> dis.dis('True == 1')
  1           0 LOAD_CONST               0 (True)
              2 LOAD_CONST               1 (1)
              4 COMPARE_OP               2 (==)
              6 RETURN_VALUE

>>> dis.dis('True + True')
  1           0 LOAD_CONST               1 (2)
              2 RETURN_VALUE

这里,它为关键字发出了一个 LOAD_CONST (True),优化器可以利用这个。所以,True + 1 区分,原因和 while True 一样。(而 == 仍然不区分它们,因为优化器不会优化掉比较操作。)


同时,如果代码没有被优化掉,解释器在这三种情况下会完全一样地对待 True1boolint 的一个子类,并且从 int 继承了大部分方法,而 True 的内部整数值是 1。所以,无论你是在做 while 测试(在 3.x 中是 __bool__,在 2.x 中是 __nonzero__)、比较(__eq__)还是算术运算(__add__),无论你使用 True 还是 1,你调用的都是同一个方法。

22

把它叫做“无限循环”其实不太准确。

因此,解释器可以把 while True: 循环当作一个无限循环来处理。

因为你还是可以在这样的 while True: 循环中跳出来。但是在 Python 3 中,这种循环的 else 部分是永远不会被执行的。

而且,Python 3 对 True 的值查找进行了简化,使得它的运行速度和 Python 2 中的 while 1 一样快。

性能比较

下面展示一个稍微复杂一点的 while 循环在时间上的差异:

准备工作

def while1():
    x = 0
    while 1:
        x += 1
        if x == 10:
            break
            
def whileTrue():
    x = 0
    while True:
        x += 1
        if x == 10:
            break

Python 2

>>> import timeit
>>> min(timeit.repeat(while1))
0.49712109565734863
>>> min(timeit.repeat(whileTrue))
0.756627082824707

Python 3

>>> import timeit
>>> min(timeit.repeat(while1))
0.6462970309949014
>>> min(timeit.repeat(whileTrue))
0.6450748789939098

解释

为了说明差异,在 Python 2 中:

>>> import keyword
>>> 'True' in keyword.kwlist
False

而在 Python 3 中:

>>> import keyword
>>> 'True' in keyword.kwlist
True
>>> True = 'true?'
  File "<stdin>", line 1
SyntaxError: can't assign to keyword

因为 True 在 Python 3 中是一个关键字,解释器不需要查找它的值来确认是否被替换成其他值。但在 Python 2 中,你可以把 True 赋值为其他值,所以解释器每次都得查找它。

Python 2 的结论

如果你在 Python 2 中有一个紧凑且运行时间长的循环,最好用 while 1: 而不是 while True:

Python 3 的结论

如果你的循环没有跳出的条件,就用 while True:

136

在Python 2.x中,True并不是一个关键字,而只是一个内置的全局常量,它在bool类型中被定义为1。因此,解释器仍然需要加载True的内容。换句话说,True是可以被重新赋值的:

Python 2.7 (r27:82508, Jul  3 2010, 21:12:11) 
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> True = 4
>>> True
4

而在Python 3.x中,它真正变成了一个关键字和一个真正的常量:

Python 3.1.2 (r312:79147, Jul 19 2010, 21:03:37) 
[GCC 4.2.1 (Apple Inc. build 5664)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> True = 4
  File "<stdin>", line 1
SyntaxError: assignment to keyword

这样,解释器就可以把while True:这个循环替换成一个无限循环。

撰写回答