为什么操作码中没有显示 if 语句?

Yix*_*uan 1 python bytecode

这是一个简单的示例:

>>> def foo():
...     if True:
...             return 'yes'
... 
>>> import dis
>>> dis.dis(foo)
  3           0 LOAD_CONST               1 ('yes')
              2 RETURN_VALUE

Run Code Online (Sandbox Code Playgroud)

为什么这里没有关于 if 语句的字节码?它只是直接返回值。

  • CPython3.6

Tho*_*erl 6

在 Python 3 中,True不能被覆盖,所以 Python 被允许“优化”它并假设它True总是正确的。

由于历史原因,Python 2 没有True作为常量/关键字,因此该语言不允许对此进行优化:

Python 2.7.16 (default, May  8 2021, 11:48:02) 
[GCC Apple LLVM 12.0.5 (clang-1205.0.19.59.6) [+internal-os, ptrauth-isa=deploy on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> def foo():
...     if True:
...         return 'yes'
... 
>>> import dis
>>> dis.dis(foo)
  2           0 LOAD_GLOBAL              0 (True)
              3 POP_JUMP_IF_FALSE       10

  3           6 LOAD_CONST               1 ('yes')
              9 RETURN_VALUE        
        >>   10 LOAD_CONST               0 (None)
             13 RETURN_VALUE        
Run Code Online (Sandbox Code Playgroud)

因此,从历史上看,在 Python 2 中,您可以(即使您永远不应该)设置True = 213或什至True = 0(或True = False)并弄乱您的程序逻辑:

>>> True = 0
>>> print(foo())
None
>>> True = 1
>>> print(foo())
yes
Run Code Online (Sandbox Code Playgroud)

在 Python 3 中,这是不可能的:

Python 3.9.5 (default, May  4 2021, 03:36:27) 
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> True = 213
  File "<stdin>", line 1
    True = 213
    ^
SyntaxError: cannot assign to True
Run Code Online (Sandbox Code Playgroud)

请注意,这if True是一种特殊情况,因为 Python 3 无法优化其他重言式(目前):

>>> def foo():
...     if 3 == 3:
...         return 'yes'
... 
>>> import dis
>>> dis.dis(foo)
  2           0 LOAD_CONST               1 (3)
              2 LOAD_CONST               1 (3)
              4 COMPARE_OP               2 (==)
              6 POP_JUMP_IF_FALSE       12

  3           8 LOAD_CONST               2 ('yes')
             10 RETURN_VALUE
        >>   12 LOAD_CONST               0 (None)
             14 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)

请注意,这是特定于实现的(CPython),在 PyPy 3 中又略有不同;它可以确定if True始终是True,但随后也在return None函数末尾为隐式生成字节码(永远不会被执行):

Python 3.7.10 (51efa818fd9b24f625078c65e8e2f6a5ac24d572, Apr 08 2021, 17:43:00)
[PyPy 7.3.4 with GCC Apple LLVM 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>> def foo():
....     if True:
....         return 'yes'
....         
>>>> import dis
>>>> dis.dis(foo)
  3           0 LOAD_CONST               1 ('yes')
              2 RETURN_VALUE
              4 LOAD_CONST               0 (None)
              6 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)

再次为了完整起见,不允许使用 Python 2.7 语义的 PyPy 优化掉True,因为它可以设置为其他内容(此处隐式代码return None是正确的,因为它可能会根据您设置的内容True执行):

Python 2.7.18 (63df5ef41012b07fa6f9eaba93f05de0eb540f88, Apr 08 2021, 15:53:40)
[PyPy 7.3.4 with GCC Apple LLVM 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>> def foo():
....     if True:
....         return 'yes'
....         
>>>> import dis
>>>> dis.dis(foo)
  2           0 LOAD_GLOBAL              0 (True)
              3 POP_JUMP_IF_FALSE       10

  3           6 LOAD_CONST               1 ('yes')
              9 RETURN_VALUE        
        >>   10 LOAD_CONST               0 (None)
             13 RETURN_VALUE        
Run Code Online (Sandbox Code Playgroud)

它是如何在 CPython 中实现的

优化在CPython的3在实施optimize_basic_block()Python/compile.c,截至本文写作这一行(“删除LOAD_CONST常量;条件跳转”)做了优化。换句话说,编译器生成这样的操作码:

  LOAD_CONST ... (True)
  POP_JUMP_IF_FALSE x
  LOAD_CONST ... ('yes')
  RETURN_VALUE
x LOAD_CONST ... (None)
  RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)

因此,如果它看到 aLOAD_CONST后跟 a POP_JUMP_IF_FALSE,并且可以证明该常量True在编译时,它将删除 theLOAD_CONST并将 thePOP_JUMP_IF_FALSE变成 aJUMP_ABSOLUTE或变成 a NOP,这取决于计算的布尔值(这里它将变成 a NOP) :

  NOP
  NOP
  LOAD_CONST ... ('yes')
  RETURN_VALUE
x LOAD_CONST ... (None)
  RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)

进一步的步骤 ( clean_basic_block()) 然后将删除 NOP(无操作),并且可能额外的步骤会找出之后的代码RETURN_VALUE已死并删除它。