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_CONST
s, 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