Skip to content
Advertisement

why if-statement not shown in opcode?

Here is a simple sample:

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

Why does here not have bytecode about if-statement? It just directly return value.

  • CPython3.6

Advertisement

Answer

In Python 3, True cannot be overridden, so Python is allowed to “optimize” this and assume True will always be true.

For historic reasons, Python 2 didn’t have True as a constant/keyword, so the language isn’t allowed to optimize this:

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        

So historically, in Python 2 you could (even though you never should) set True = 213 or even True = 0 (or True = False) and mess up your program logic:

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

In Python 3, this is not possible:

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

Do note that if True is a special case, as Python 3 cannot optimize other tautologies (yet):

>>> 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

Note that this is implementation-specific (CPython), in PyPy 3 it’s slightly different again; it can figure out that if True is always True, but then also generates byte code for the implicit return None at the end of the function (that never gets executed):

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

And again for completeness’ sake, PyPy with Python 2.7 semantics isn’t allowed to optimize away the True, as it could be set to something else (here the code for the implicit return None is correct, as it may get executed depending on what you set True to):

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        

How it is implemented in CPython

The optimization is implemented in CPython 3 in optimize_basic_block() in Python/compile.c, as of this writing this line (“Remove LOAD_CONST const; conditional jump”) does the optimization. In other words, the compiler generates opcode like this:

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

So if it sees a LOAD_CONST followed by a POP_JUMP_IF_FALSE, and it can prove that the constant is True at compile time, it will remove the LOAD_CONST and will turn the POP_JUMP_IF_FALSE into a JUMP_ABSOLUTE or into a NOP, depending on the boolean value calculated (here it will turn it into a NOP):

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

Further steps (clean_basic_block()) would then remove the NOPs (no operation) and probably additional steps would figure out that code after RETURN_VALUE is dead and remove that.

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement