Python statement gives unexpected answer

print 'a' in 'ab' 

outputs True , and

 print 'a' in 'ab' == True 

outputs False .

One can guess why?

+4
source share
3 answers

Operator chain at work.

 'a' in 'ab' == True 

equivalently

 '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 documents mentioned above:

The comparison can be braced arbitrarily, for example, x <y <= z is equivalent to x <y and y <= z, except that y is evaluated only once (but in both cases z are not evaluated at all when x <y is considered false).

Formally, if a, b, c, ..., y, z are expressions and op1, op2, ..., opN are comparison operators, then op1 b op2 c ... y opN z is equivalent to the operations op1 b and b op2 c and ... y opN z, except that each expression is evaluated no more than once.

The real advantage of the chain of operators is that each expression is evaluated once, at most. Thus, with a < b < c , b is evaluated only once, and then first compared with a and secondly (if necessary) to c .

As a more specific example, consider the expression 0 < x < 5 . Semantically, we want to say that x is in a closed range [0.5]. Python fixes this by evaluating the logically equivalent expression 0 < x and x < 5 . Hope, which somewhat clarifies the purpose of the operator chain.

+5
source
 >>> 'a' in 'ab' == True False >>> ('a' in 'ab') == True True 

Let's see what the first option 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 matches 'a' in 'ab' and 'ab' == True .

  • After the first two LOAD_CONST s, 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 ) acts on the top two elements, as in 'a' in 'ab' . The result of this ( True ) is stored on the stack, and 'ab' and 'a' are deleted. Now the stack consists of:
    • True
    • 'ab'
  • JUMP_IF_FALSE_OR_POP - short circuit and : if the value at the top of the stack at this point is False , we know that the whole expression will be False , so we will skip the second upcoming comparison. In this case, however, the upper value is True , so we set this value and continue the next comparison. Now the stack consists of:
    • 'ab'
  • LOAD_GLOBAL loads True , and COMPARE_OP ( == ) compares this with the remaining stack element '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 complete list of bytecode instructions here )

Combining all this, we have 'a' in 'ab' and 'ab' == True .

Now 'ab' == True is False :

 >>> 'ab' == True False 

means that the whole expression will be False .

+5
source

The reason is because Python treats a <op1> b <op2> c as a <op1> b and b <op2> c . For example, 0 < i < n is true if 0 < i and i < n . Thus, the version without parentheses checks if 'a' in 'ab' (this part is true) and 'ab' == True (this part is false, so the whole expression is false).

A β€œsimple” fix is ​​a pair of parentheses because it is a control bar to the behavior described above, but the best option is simply not explicitly compared to Boolean elements.

+2
source

Source: https://habr.com/ru/post/1493734/


All Articles