• Last edited on Apr 2, 2015, 6:15:18 PM by vaultah

Thanks to Martijn Pieters' Stackoverflow Answer


The CPython interpreter replaces the second form with the first.

That’s because loading the tuple from a constant is one operation, but the list would be 3 operations; load the two integer contents and build a new list object.

Because you are using a list literal that isn’t otherwise reachable, it is substituted for a tuple:

>>> import dis
>>> dis.dis(compile('if number in [1, 2]: pass', '<stdin>', 'exec'))
  1           0 LOAD_NAME                0 (number)
              3 LOAD_CONST               3 ((1, 2))
              6 COMPARE_OP               6 (in)
              9 POP_JUMP_IF_FALSE       15
             12 JUMP_FORWARD             0 (to 15)
        >>   15 LOAD_CONST               2 (None)
             18 RETURN_VALUE        

Here the second bytecode loads a (1, 2) tuple as a constant, in one step. Compare this to creating a list object not used in a membership test:

>>> dis.dis(compile('[1, 2]', '<stdin>', 'exec'))
  1           0 LOAD_CONST               0 (1)
              3 LOAD_CONST               1 (2)
              6 BUILD_LIST               2
              9 POP_TOP
             10 LOAD_CONST               2 (None)
             13 RETURN_VALUE

Here N+1 steps are required for a list object of length N.

This substitution is a CPython-specific peephole optimisation; see the Python/peephole.c source. For other Python implementations then, you want to stick with immutable objects instead.

That said, the best option when using Python 3.2 and up, is to use a set literal:

if number in {1, 2}:

as the peephole optimiser will replace that with a frozenset() object and membership tests against sets are O(1) constant operations:

>>> dis.dis(compile('if number in {1, 2}: pass', '<stdin>', 'exec'))
  1           0 LOAD_NAME                0 (number)
              3 LOAD_CONST               3 (frozenset({1, 2}))
              6 COMPARE_OP               6 (in)
              9 POP_JUMP_IF_FALSE       15
             12 JUMP_FORWARD             0 (to 15)
        >>   15 LOAD_CONST               2 (None)
             18 RETURN_VALUE

This optimization was added in Python 3.2 but wasn’t backported to Python 2.

As such, the Python 2 optimiser doesn’t recognize this option and the cost of building either a set or frozenset from the contents is almost guaranteed to be more costly than using a tuple for the test.