What is the best approach in python: multiple OR or IN in if statement? Considering performance and best pratices.
if cond == '1' or cond == '2' or cond == '3' or cond == '4': pass
OR
if cond in ['1','2','3','4']: pass
                The best approach is to use a set:
if cond in {'1','2','3','4'}:
as membership testing in a set is O(1) (constant cost).
The other two approaches are equal in complexity; merely a difference in constant costs. Both the in test on a list and the or chain short-circuit; terminate as soon as a match is found. One uses a sequence of byte-code jumps (jump to the end if True), the other uses a C-loop and an early exit if the value matches. In the worst-case scenario, where cond does not match an element in the sequence either approach has to check all elements before it can return False. Of the two, I'd pick the in test any day because it is far more readable.
This actually depends on the version of Python. In Python 2.7 there were no set constants in the bytecode, thus in Python 2 in the case of a fixed constant small set of values use a tuple:
if x in ('2', '3', '5', '7'):
    ...
A tuple is a constant:
>>> dis.dis(lambda: item in ('1','2','3','4'))
  1           0 LOAD_GLOBAL              0 (item)
              3 LOAD_CONST               5 (('1', '2', '3', '4'))
              6 COMPARE_OP               6 (in)
              9 RETURN_VALUE
Python is also smart enough to optimize a constant list on Python 2.7 to a tuple:
>>> dis.dis(lambda: item in ['1','2','3','4'])
  1           0 LOAD_GLOBAL              0 (item)
              3 LOAD_CONST               5 (('1', '2', '3', '4'))
              6 COMPARE_OP               6 (in)
              9 RETURN_VALUE        
But Python 2.7 bytecode (and compiler) lacks support for constant sets:
>>> dis.dis(lambda: item in {'1','2','3','4'})
  1           0 LOAD_GLOBAL              0 (item)
              3 LOAD_CONST               1 ('1')
              6 LOAD_CONST               2 ('2')
              9 LOAD_CONST               3 ('3')
             12 LOAD_CONST               4 ('4')
             15 BUILD_SET                4
             18 COMPARE_OP               6 (in)
             21 RETURN_VALUE        
Which means that the set in if condition needs to be rebuilt for each test.
However in Python 3.4 the bytecode supports set constants; there the code evaluates to:
>>> dis.dis(lambda: item in {'1','2','3','4'})
  1           0 LOAD_GLOBAL              0 (item)
              3 LOAD_CONST               5 (frozenset({'4', '2', '1', '3'}))
              6 COMPARE_OP               6 (in)
              9 RETURN_VALUE
As for the multi-or code, it produces totally hideous bytecode:
>>> dis.dis(lambda: item == '1' or item == '2' or item == '3' or item == '4')
  1           0 LOAD_GLOBAL              0 (item)
              3 LOAD_CONST               1 ('1')
              6 COMPARE_OP               2 (==)
              9 JUMP_IF_TRUE_OR_POP     45
             12 LOAD_GLOBAL              0 (item)
             15 LOAD_CONST               2 ('2')
             18 COMPARE_OP               2 (==)
             21 JUMP_IF_TRUE_OR_POP     45
             24 LOAD_GLOBAL              0 (item)
             27 LOAD_CONST               3 ('3')
             30 COMPARE_OP               2 (==)
             33 JUMP_IF_TRUE_OR_POP     45
             36 LOAD_GLOBAL              0 (item)
             39 LOAD_CONST               4 ('4')
             42 COMPARE_OP               2 (==)
        >>   45 RETURN_VALUE        
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With