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

why if-statement not shown in opcode?

这是一个简单的示例:

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

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

在 Python 3 中,True 不能被覆盖,所以 Python 被允许“优化”这个并假设 True 永远是真的。

由于历史原因,Python 2 没有 True 作为 constant/keyword,因此不允许语言对此进行优化:

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        

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

>>> True = 0
>>> print(foo())
None
>>> True = 1
>>> print(foo())
yes

在Python3中,这是不可能的:

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

请注意 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

请注意,这是特定于实现的 (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

再次为了完整起见,具有 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        

在C中是如何实现的Python

优化在CPython 3 in optimize_basic_block() in Python/compile.c中实现,截至本文撰写时this line(“删除LOAD_CONST const;条件jump") 进行优化。换句话说,编译器生成这样的操作码:

  LOAD_CONST ... (True)
  POP_JUMP_IF_FALSE x
  LOAD_CONST ... ('yes')
  RETURN_VALUE
x LOAD_CONST ... (None)
  RETURN_VALUE

所以如果它看到一个LOAD_CONST后面跟着一个POP_JUMP_IF_FALSE,并且它可以在编译时证明常量是True,它会删除LOAD_CONST并将 POP_JUMP_IF_FALSE 转换为 JUMP_ABSOLUTENOP,具体取决于计算的布尔值(此处它将转换为 NOP):

  NOP
  NOP
  LOAD_CONST ... ('yes')
  RETURN_VALUE
x LOAD_CONST ... (None)
  RETURN_VALUE

进一步的步骤 (clean_basic_block()) 将删除 NOP(无操作)并且可能额外的步骤将在 RETURN_VALUE 死后找出该代码并将其删除。