print 'a' in 'ab'
prints True, while
print 'a' in 'ab' == True
prints False.
Any guess why?
Operator chaining at work.
'a' in 'ab' == True
is equivalent to
'a' in 'ab' and 'ab' == True
Take a look:
>>> 'a' in 'ab' == True
False
>>> ('a' in 'ab') == True
True
>>> 'a' in ('ab' == True)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: argument of type 'bool' is not iterable
>>> 'a' in 'ab' and 'ab' == True
False
From the docs linked above:
Comparisons can be chained arbitrarily, e.g., x < y <= z is equivalent to x < y and y <= z, except that y is evaluated only once (but in both cases z is not evaluated at all when x < y is found to be false).
Formally, if a, b, c, ..., y, z are expressions and op1, op2, ..., opN are comparison operators, then a op1 b op2 c ... y opN z is equivalent to a op1 b and b op2 c and ... y opN z, except that each expression is evaluated at most once.
The real advantage of operator chaining is that each expression is evaluated once, at most. So with a < b < c, b is only evaluated once and then compared first to a and secondly (if necesarry) to c.
As a more concrete example, lets consider the expression 0 < x < 5. Semantically, we mean to say that x is in the closed range [0,5]. Python captures this by evaluating the logically equivalent expression 0 < x and x < 5. Hope that clarifies the purpose of operator chaining somewhat.
>>> 'a' in 'ab' == True
False
>>> ('a' in 'ab') == True
True
Let's take a look at what the first variant actually means:
>>> import dis
>>> def f():
...     'a' in 'ab' == True
... 
>>> dis.dis(f)
  2           0 LOAD_CONST               1 ('a')
              3 LOAD_CONST               2 ('ab')
              6 DUP_TOP             
              7 ROT_THREE           
              8 COMPARE_OP               6 (in)
             11 JUMP_IF_FALSE_OR_POP    23
             14 LOAD_GLOBAL              0 (True)
             17 COMPARE_OP               2 (==)
             20 JUMP_FORWARD             2 (to 25)
        >>   23 ROT_TWO             
             24 POP_TOP             
        >>   25 POP_TOP             
             26 LOAD_CONST               0 (None)
             29 RETURN_VALUE  
If you follow the bytecode, you will see that this equates to 'a' in 'ab' and 'ab' == True. 
LOAD_CONSTs, the stack consists of:
'ab''a'DUP_TOP, the stack consists of:
'ab''ab''a'ROT_THREE, the stack consists of:
'ab''a''ab'in (COMPARE_OP) operates on the top two elements, as in 'a' in 'ab'. The result of this (True) is stored on the stack, while 'ab' and 'a' are popped off. Now the stack consists of:
True'ab'JUMP_IF_FALSE_OR_POP is the short-circuiting of and: if the value on top of the stack at this point is False we know that the entire expression will be False, so we skip over the second upcoming comparison. In this case, however, the top value is True, so we pop this value and continue on to the next comparison. Now the stack consists of:
'ab'LOAD_GLOBAL loads True, and COMPARE_OP (==) compares this with the remaining stack item, 'ab', as in 'ab' == True. The result of this is stored on the stack, and this is the result of the entire statement.(you can find a full list of bytecode instructions here)
Putting it all together, we have 'a' in 'ab' and 'ab' == True.
Now, 'ab' == True is False:
>>> 'ab' == True
False
meaning the entire expression will be False.
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