while (1) 与 while(True) 的区别是什么(在Python 2字节码中)?
我对一个关于 Perl 中无限循环的问题很感兴趣:while (1) 和 for (;;) 之间有速度差异吗?,于是决定在 Python 中做个类似的比较。我原本以为在 while(True): pass
和 while(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 个回答
这是一个七年前的问题,已经有了很好的回答,但问题中有一个误解没有在任何回答中提到,这可能会让一些被标记为重复的问题感到困惑。
在其他情况下,Python表现得好像 True 等于 1:
>>> True == 1
True
>>> True + True
2
那为什么 while 会区分这两者呢?
实际上,while
在这里并没有做任何不同的事情。它区分 1
和 True
的方式和 +
的例子是完全一样的。
这是 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
区分 1
和 True
的原因和 +
是完全一样的。(而 ==
不区分它们,因为优化器不会优化掉比较操作。)
现在比较 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
一样。(而 ==
仍然不区分它们,因为优化器不会优化掉比较操作。)
同时,如果代码没有被优化掉,解释器在这三种情况下会完全一样地对待 True
和 1
。bool
是 int
的一个子类,并且从 int
继承了大部分方法,而 True
的内部整数值是 1。所以,无论你是在做 while
测试(在 3.x 中是 __bool__
,在 2.x 中是 __nonzero__
)、比较(__eq__
)还是算术运算(__add__
),无论你使用 True
还是 1
,你调用的都是同一个方法。
把它叫做“无限循环”其实不太准确。
因此,解释器可以把
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:
。
在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:
这个循环替换成一个无限循环。