摘要 :使用 Python 2 编程的时候,经常会看到有人说要用 while 1 来替代 while True,这是为什么呢?本文将带你使用 dis 内置模块来分析为什么是这样。

Python 代码是先被编译为 Python 字节码(Byte Code,文件后缀为 .pyc)后,再由 Python 虚拟机来执行 Python 字节码。

一般来说一个 Python 语句会对应若干字节码指令,Python 的字节码是一种类似汇编指令的中间语言, 但是一个字节码指令并不是对应一个机器指令(二进制指令),而是对应一段 C 代码(可以查看 cevel.c ), 而不同的指令的性能不同,所以一般不能单单通过指令数量来判断代码的性能。

Python 提供了 dis (Disassembler for Python bytecode,反汇编 Python 代码为字节码) 内置模块来分析和评估 Python 的性能和质量。

while 1 真的比 while True 好吗

我们通过以下代码进行演示:

$ python2
>>> def f1():
...   while 1:
...     pass
... 
>>> def f2():
...   while True:
...     pass
... 
>>> import dis
>>> dis.dis(f1)
  2           0 SETUP_LOOP               4 (to 7)

  3     >>    3 JUMP_ABSOLUTE            3
              6 POP_BLOCK           
        >>    7 LOAD_CONST               0 (None)
             10 RETURN_VALUE        
>>> dis.dis(f2)
  2           0 SETUP_LOOP              10 (to 13)
        >>    3 LOAD_GLOBAL              0 (True)
              6 POP_JUMP_IF_FALSE       12

  3           9 JUMP_ABSOLUTE            3
        >>   12 POP_BLOCK           
        >>   13 LOAD_CONST               0 (None)
             16 RETURN_VALUE

参考官方文档说明 ,输出分为以下几列:

  • 第一列:行号,用于每一行的第一条指令
  • 第二列:当前执行的指令,用 --> 表示;
  • 第三列:一个带标签(Labelled)的指令,用 >> 表示;
  • 第四列:指令的地址;
  • 第五列:操作代码名;
  • 第六列:操作参数;
  • 第七列:用括号括起来的参数解释。

对比发现,while True版本比while 1版本对应的字节码多了以下两条:

>>    3 LOAD_GLOBAL              0 (True)
      6 POP_JUMP_IF_FALSE       12

所以while True性能上也会有一定损失,while 1确实好一些。

使用 timeit 进行验证

我们使用 timeit 模块测试一下耗时情况:

>>> def f1():
...   n = 1000
...   while 1:
...     n -= 1
...     if n < 0:
...       break
... 
>>> def f2():
...   n = 1000
...   while True:
...     n -= 1
...     if n < 0:
...       break
... 
>>> import timeit
>>> timeit.timeit('f1()', number=100000, setup='from __main__ import f1, f2')
2.807568073272705
>>> timeit.timeit('f2()', number=100000, setup='from __main__ import f1, f2')
4.125874996185303

果然,对于上面简单的测试代码,while 1版本确实快不少。

为什么 while True 生成更多字节码

原来在 Python 2 中,由于历史原因,TrueFalse被实现为内置变量,而不是 Python 关键字,所以在使用的时候需要执行LOAD_GLOBAL操作。

$ python2
>>> True = False  # 可以对 True 内置变量进行覆盖
>>> True is False
True

但是在 Python 3 中,它们都已经变成关键字:

$ python3
>>> True = False
  File "<stdin>", line 1
SyntaxError: can't assign to keyword

所以 Python 3 以后,while 1while True编成的字节码就是一样的了:

$ python3
>>> def f1():
...   while 1:
...     pass
... 
>>> def f2():
...   while True:
...     pass
... 
>>> import dis
>>> dis.dis(f1)
  2           0 SETUP_LOOP               4 (to 6)

  3     >>    2 JUMP_ABSOLUTE            2
              4 POP_BLOCK
        >>    6 LOAD_CONST               0 (None)
              8 RETURN_VALUE
>>> dis.dis(f2)
  2           0 SETUP_LOOP               4 (to 6)

  3     >>    2 JUMP_ABSOLUTE            2
              4 POP_BLOCK
        >>    6 LOAD_CONST               0 (None)
              8 RETURN_VALUE

结论

我们通过使用 dis 模块分析了在 Python 2 中为什么while 1while True快,也发现在 Python 3 中,由于True变成关键字,所以两者性能都一样。

其实不管是 Python 2 还是 Python 3,除非是性能要求非常高的关键代码,否则都用while True就好了,毕竟使用 Python 的目的之一,就是要让代码有更好的可读性。

扩展

  1. 读者可以使用本分介绍的 dis 模块,分析一下为什么 if not xif x is None 快。
  2. Python boolean 类型继承自 int,所以 isinstance (True, int) 将返回 TrueTrue == 1 也返回 True