Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python statement giving unexpected answer [duplicate]

Tags:

python

boolean

print 'a' in 'ab'

prints True, while

print 'a' in 'ab' == True

prints False.

Any guess why?

like image 423
Lokesh Avatar asked Dec 16 '22 08:12

Lokesh


2 Answers

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.

like image 113
Brian Avatar answered Jan 01 '23 06:01

Brian


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

  • After the first two LOAD_CONSTs, the stack consists of:
    • 'ab'
    • 'a'
  • After DUP_TOP, the stack consists of:
    • 'ab'
    • 'ab'
    • 'a'
  • After ROT_THREE, the stack consists of:
    • 'ab'
    • 'a'
    • 'ab'
  • At this point, 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.

like image 31
arshajii Avatar answered Jan 01 '23 05:01

arshajii