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.