This question already has answers here:
Why does the expression 0 < 0 == 0 return False in Python?
(9 answers)
Closed 5 years ago.
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.
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.
The reason is that 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. So the version without parentheses checks whether 'a' in 'ab' (this part is true) and 'ab' == True (this part is false, so the whole expression is false).
The "simple" fix is a pair of parentheses because that is an escape hatch to the behavior described above, but a better option is just not comparing with booleans explicitly.
Related
I am a new learner in programming, and I was trying to check whether a word exists in a file. I put the words into a dictionary (the exercise told me to put it in a dict as a key and ignore the value), and then let the user input a word, then I tried to check whether it is in the dictionary.
words_dict = dict()
fhand = open('desktop/codingMaterial/words.txt')
for line in fhand:
line = line.rstrip()
t = line.split()
for word in t:
words_dict[word] = 0
# check if word exists
v = input('Enter a variable: ')
if v in words_dict == True:
print('Exist.')
else:
print('Does not exist.')
I tried to run this, but no matter which word I input, the output was always 'Does not exist.'. I think I did something wrong in the if statement, but I am not sure how to fix it.
if v in word_dict == True is evluated as if v in word_dict and word_dict==True. This is called operator chaining.
Check this byte-code using dis module.
import dis
a={'a':1,'b':2,'c':3}
In [53]: dis.dis('"a" in a == True')
1 0 LOAD_CONST 0 ('a')
2 LOAD_NAME 0 (a)
4 DUP_TOP
6 ROT_THREE
8 COMPARE_OP 6 (in)
10 JUMP_IF_FALSE_OR_POP 18
12 LOAD_CONST 1 (True)
14 COMPARE_OP 2 (==)
16 RETURN_VALUE
>> 18 ROT_TWO
20 POP_TOP
22 RETURN_VALUE
In [54]: dis.dis('"a" in a and a==True')
1 0 LOAD_CONST 0 ('a')
2 LOAD_NAME 0 (a)
4 COMPARE_OP 6 (in)
6 JUMP_IF_FALSE_OR_POP 14
8 LOAD_NAME 0 (a)
10 LOAD_CONST 1 (True)
12 COMPARE_OP 2 (==)
>> 14 RETURN_VALUE
Both are evaluated in the same way. And in your case word_dict==True is always False as word_dict is a dictionary. So, it would never enter the if block and else block is executed.
if some_bool_expr == True and can be written as if some_bool_expr and if some_bool_expr==False can be written as if not some_bool_expr.
Byte-code Instructions documentation link
LOAD_CONST and LOAD_NAME: push value onto the stack. After line 2 top of the stack is a(not 'a')
DUP_TOP: Duplicates the reference on top of the stack and pushes onto the stack. Now top of the stack is a.
ROT_THREE: Lifts second and third stack item one position up moves top down to position three. Now TOS(top of the stack) is third element (a) and 2nd element (a) is now TOS, 3rd element 'a' is now 2nd element.
COMPARE_OP: Tells the interpreter to pop the two topmost stack elements and perform an membership test(in) between them, pushing the Boolean result back onto the stack. 'a' in a is done and result is pushed onto the stack i.e True. Now stack has TOS as True and duplicate reference from DUP_TOP below it.
JUMP_IF_FALSE_OR_POP: If TOS is false, sets the bytecode counter to target and leaves TOS on the stack. Otherwise (TOS is true), TOS is popped. In our example, TOS is True so. TOS is popped. Now TOS is a.
LOAD_CONST True is pushed onto the stack. COMPARE_OP ==. True==a is done which False. Now TOS is False.
RETURN_VALUE: Returns with TOS to the caller of the function. In our example, TOS is False at this point. False is returned.
POP_TOP: Removes the top-of-stack (TOS) item.
The only difference between both the expressions is that a is evaluate twice in the 2nd one.
Also refer to this answer: https://stackoverflow.com/a/3299724/12416453
A test of how x in y == z gets evaluated, by using wrappers that print what's going on:
class C:
def __init__(self, value):
self.value = value
def __eq__(self, other):
print(self, '==', other)
return True
def __contains__(self, value):
print(value, 'in', self)
return True
def __str__(self):
return str(self.value)
C(1) in C(2) == C(3)
Prints 1 in 2 followed by 2 == 3, as it should.
Not 1 in 2 followed by True == 3.
Not 2 == 3 followed by 1 in True.
Remove the == True.
It should be just .
if v in words_dict:
print('Exist.')
change if v in words_dict == True: with if v in words_dict:
your issue is related to Chaining comparison operators, both operators in and == have the same precedence and are evaluated left-to-right, an equivalent to your code is (already pointed by #Ch3steR):
if v in word_dict and word_dict==True
Verifying that the Python interpreter evaluates the subexpression words_dict == True first. And not v in words_dict (wrong assumption has been made). I see from other posts that this statement is not accurate about what the interpreter actually does. Which is pretty interesting. From other descriptions here, it appears the interp regards the in operator as being at the same level of precedence as ==. In either case, you could group the first expression to force the order of evaluation.
>>> words_dict == True
False
>>> 'hand' in words_dict == True
False
>>> 'hand' in words_dict
True
>>> ('hand' in words_dict) == True
True
If this were an example of production code, one could implement this logic as (a in b) == c if that was the intent of the expression. Or if the expression were truly intended to be a in b == c as discussed, then it could be implemented explicitly as (a in b) and (b == c).
In many coding style specifications I've seen, there's a requirement that developers use parenthesis to group their subexpressions in boolean expressions. Whether or not the author believes he has a solid grasp of order-of-precedence (without parenthesis), other developers appreciate the diligence and no-assumptions approach of using parenthesis, not only as a safeguard to ensure there aren't any incorrect assumptions, but to show the other developers the intended order of precedence with no ambiguity. This is a good case-in-point.
There are tables showing the order of precedence of operators online that list in below ==. However, this one is directly from docs.python.org and accurately shows that in and == are at the same level: https://docs.python.org/3/reference/expressions.html#operator-precedence
i was testing the precedence of comparison and membership operator, as per Python documentation they are at same precedence. But it is showing strange results as follows,
If anyone can justify following code and the corresponding output..
print( ( True!= 12) in (12,14)) #output: False
print( True!= 12 in (12,14)) #output: True
print( True!= (12 in (12,14))) #output: False
From https://docs.python.org/3/reference/expressions.html:
Note that comparisons, membership tests, and identity tests, all have the same precedence and have a left-to-right chaining feature as described in the Comparisons section.
In the Comparisons section we find:
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).
So, your second line is equivalent to:
True != 12 and 12 in (12, 14)
which evaluates to True and True which evaluates to True.
Similarly curious constructions are:
>>> True == True in (True, False)
True
>>> True == False in (True, False)
False
>>> 2 == 2 in (2, 3)
True
>>> 1 == 1 in (2, 3)
False
>>> 1 != 2 in (2, 3)
True
>>> 2 != 1 in (2, 3)
False
>>> 2 in (2, 3) in [True, False]
False
First Case:
print((True != 12) in (12,14))
We can say that first we evaluate True != 12 condition which give us True, and then we are evaluating if True is in the tuple (12,14) and that's False.
Third Case:
print(True != (12 in (12,14)))
We can say that first we evaluate (12 in (12,14)) condition which give us True because 12 is in the tuple (12,14). Then we evaluate if True != True an that's False.
Second Case:
print( True != 12 in (12,14))
So here is the tricky one. Without the parentheses we don't have where to start evaluating the conditions. To explain this case, I'm gonna give you the next example: if you try the following code (which is pretty similar to the one that we are trying to figure out the result):
print(1 != 2 != 3) #output True
You are gonna get True and that's because 1 != 2 and 2 != 3. So as you may have already noticed, it's an implicit AND. The above code is equal to:
print(1 != 2 and 2 != 3) #output True
So if we take this logic to our case, we are evaluating this:
print( True != 12 and 12 in (12,14)) # => (True and True) #output True
Looking into Queue.py in Python 2.6, I found this construct that I found a bit strange:
def full(self):
"""Return True if the queue is full, False otherwise
(not reliable!)."""
self.mutex.acquire()
n = 0 < self.maxsize == self._qsize()
self.mutex.release()
return n
If maxsize is 0 the queue is never full.
My question is how does it work for this case? How 0 < 0 == 0 is considered False?
>>> 0 < 0 == 0
False
>>> (0) < (0 == 0)
True
>>> (0 < 0) == 0
True
>>> 0 < (0 == 0)
True
I believe Python has special case handling for sequences of relational operators to make range comparisons easy to express. It's much nicer to be able to say 0 < x <= 5 than to say (0 < x) and (x <= 5).
These are called chained comparisons. And that's a link to the documentation for them.
With the other cases you talk about, the parentheses force one relational operator to be applied before the other, and so they are no longer chained comparisons. And since True and False have values as integers you get the answers you do out of the parenthesized versions.
Because
(0 < 0) and (0 == 0)
is False. You can chain together comparison operators and they are automatically expanded out into the pairwise comparisons.
EDIT -- clarification about True and False in Python
In Python True and False are just instances of bool, which is a subclass of int. In other words, True really is just 1.
The point of this is that you can use the result of a boolean comparison exactly like an integer. This leads to confusing things like
>>> (1==1)+(1==1)
2
>>> (2<1)<1
True
But these will only happen if you parenthesise the comparisons so that they are evaluated first. Otherwise Python will expand out the comparison operators.
The strange behavior your experiencing comes from pythons ability to chain conditions. Since it finds 0 is not less than 0, it decides the entire expression evaluates to false. As soon as you break this apart into seperate conditions, you're changing the functionality. It initially is essentially testing that a < b && b == c for your original statement of a < b == c.
Another example:
>>> 1 < 5 < 3
False
>>> (1 < 5) < 3
True
>>> 0 < 0 == 0
False
This is a chained comparison. It returns true if each pairwise comparison in turn is true. It is the equivalent to (0 < 0) and (0 == 0)
>>> (0) < (0 == 0)
True
This is equivalent to 0 < True which evaluates to True.
>>> (0 < 0) == 0
True
This is equivalent to False == 0 which evaluates to True.
>>> 0 < (0 == 0)
True
Equivalent to 0 < True which, as above, evaluates to True.
Looking at the disassembly (the bytes codes) it is obvious why 0 < 0 == 0 is False.
Here is an analysis of this expression:
>>>import dis
>>>def f():
... 0 < 0 == 0
>>>dis.dis(f)
2 0 LOAD_CONST 1 (0)
3 LOAD_CONST 1 (0)
6 DUP_TOP
7 ROT_THREE
8 COMPARE_OP 0 (<)
11 JUMP_IF_FALSE_OR_POP 23
14 LOAD_CONST 1 (0)
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
Notice lines 0-8: These lines check if 0 < 0 which obviously returns False onto the python stack.
Now notice line 11: JUMP_IF_FALSE_OR_POP 23
This means that if 0 < 0 returns False perform a jump to line 23.
Now, 0 < 0 is False, so the jump is taken, which leaves the stack with a False which is the return value for the whole expression 0 < 0 == 0, even though the == 0 part isn't even checked.
So, to conclude, the answer is like said in other answers to this question.
0 < 0 == 0 has a special meaning. The compiler evaluates this to two terms: 0 < 0 and 0 == 0. As with any complex boolean expressions with and between them, if the first fails then the second one isn't even checked.
Hopes this enlightens things up a bit, and I really hope that the method I used to analyse this unexpected behavior will encourage others to try the same in the future.
As other's mentioned x comparison_operator y comparison_operator z is syntactical sugar for (x comparison_operator y) and (y comparison_operator z) with the bonus that y is only evaluated once.
So your expression 0 < 0 == 0 is really (0 < 0) and (0 == 0), which evaluates to False and True which is just False.
maybe this excerpt from the docs can help:
These are the so-called “rich
comparison” methods, and are called
for comparison operators in preference
to __cmp__() below. The correspondence
between operator symbols and method
names is as follows: x<y calls
x.__lt__(y), x<=y calls x.__le__(y),
x==y calls x.__eq__(y), x!=y and x<>y
call x.__ne__(y), x>y calls
x.__gt__(y), and x>=y calls
x.__ge__(y).
A rich comparison method may return
the singleton NotImplemented if it
does not implement the operation for a
given pair of arguments. By
convention, False and True are
returned for a successful comparison.
However, these methods can return any
value, so if the comparison operator
is used in a Boolean context (e.g., in
the condition of an if statement),
Python will call bool() on the value
to determine if the result is true or
false.
There are no implied relationships
among the comparison operators. The
truth of x==y does not imply that x!=y
is false. Accordingly, when defining
__eq__(), one should also define __ne__() so that the operators will behave as expected. See the paragraph
on __hash__() for some important notes
on creating hashable objects which
support custom comparison operations
and are usable as dictionary keys.
There are no swapped-argument versions
of these methods (to be used when the
left argument does not support the
operation but the right argument
does); rather, __lt__() and __gt__()
are each other’s reflection, __le__()
and __ge__() are each other’s
reflection, and __eq__() and __ne__()
are their own reflection.
Arguments to rich comparison methods
are never coerced.
These were comparisons but since you are chaining comparisons you should know that:
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.
Here it is, in all its glory.
>>> class showme(object):
... def __init__(self, name, value):
... self.name, self.value = name, value
... def __repr__(self):
... return "<showme %s:%s>" % (self.name, self.value)
... def __cmp__(self, other):
... print "cmp(%r, %r)" % (self, other)
... if type(other) == showme:
... return cmp(self.value, other.value)
... else:
... return cmp(self.value, other)
...
>>> showme(1,0) < showme(2,0) == showme(3,0)
cmp(<showme 1:0>, <showme 2:0>)
False
>>> (showme(1,0) < showme(2,0)) == showme(3,0)
cmp(<showme 1:0>, <showme 2:0>)
cmp(<showme 3:0>, False)
True
>>> showme(1,0) < (showme(2,0) == showme(3,0))
cmp(<showme 2:0>, <showme 3:0>)
cmp(<showme 1:0>, True)
True
>>>
I'm thinking Python is doing it's weird between magic. Same as 1 < 2 < 3 means 2 is between 1 and 3.
In this case, I think it's doing [middle 0] is greater than [left 0] and equal to [right 0]. Middle 0 is not greater than left 0, so it evaluates to false.
Looking into Queue.py in Python 2.6, I found this construct that I found a bit strange:
def full(self):
"""Return True if the queue is full, False otherwise
(not reliable!)."""
self.mutex.acquire()
n = 0 < self.maxsize == self._qsize()
self.mutex.release()
return n
If maxsize is 0 the queue is never full.
My question is how does it work for this case? How 0 < 0 == 0 is considered False?
>>> 0 < 0 == 0
False
>>> (0) < (0 == 0)
True
>>> (0 < 0) == 0
True
>>> 0 < (0 == 0)
True
I believe Python has special case handling for sequences of relational operators to make range comparisons easy to express. It's much nicer to be able to say 0 < x <= 5 than to say (0 < x) and (x <= 5).
These are called chained comparisons. And that's a link to the documentation for them.
With the other cases you talk about, the parentheses force one relational operator to be applied before the other, and so they are no longer chained comparisons. And since True and False have values as integers you get the answers you do out of the parenthesized versions.
Because
(0 < 0) and (0 == 0)
is False. You can chain together comparison operators and they are automatically expanded out into the pairwise comparisons.
EDIT -- clarification about True and False in Python
In Python True and False are just instances of bool, which is a subclass of int. In other words, True really is just 1.
The point of this is that you can use the result of a boolean comparison exactly like an integer. This leads to confusing things like
>>> (1==1)+(1==1)
2
>>> (2<1)<1
True
But these will only happen if you parenthesise the comparisons so that they are evaluated first. Otherwise Python will expand out the comparison operators.
The strange behavior your experiencing comes from pythons ability to chain conditions. Since it finds 0 is not less than 0, it decides the entire expression evaluates to false. As soon as you break this apart into seperate conditions, you're changing the functionality. It initially is essentially testing that a < b && b == c for your original statement of a < b == c.
Another example:
>>> 1 < 5 < 3
False
>>> (1 < 5) < 3
True
>>> 0 < 0 == 0
False
This is a chained comparison. It returns true if each pairwise comparison in turn is true. It is the equivalent to (0 < 0) and (0 == 0)
>>> (0) < (0 == 0)
True
This is equivalent to 0 < True which evaluates to True.
>>> (0 < 0) == 0
True
This is equivalent to False == 0 which evaluates to True.
>>> 0 < (0 == 0)
True
Equivalent to 0 < True which, as above, evaluates to True.
Looking at the disassembly (the bytes codes) it is obvious why 0 < 0 == 0 is False.
Here is an analysis of this expression:
>>>import dis
>>>def f():
... 0 < 0 == 0
>>>dis.dis(f)
2 0 LOAD_CONST 1 (0)
3 LOAD_CONST 1 (0)
6 DUP_TOP
7 ROT_THREE
8 COMPARE_OP 0 (<)
11 JUMP_IF_FALSE_OR_POP 23
14 LOAD_CONST 1 (0)
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
Notice lines 0-8: These lines check if 0 < 0 which obviously returns False onto the python stack.
Now notice line 11: JUMP_IF_FALSE_OR_POP 23
This means that if 0 < 0 returns False perform a jump to line 23.
Now, 0 < 0 is False, so the jump is taken, which leaves the stack with a False which is the return value for the whole expression 0 < 0 == 0, even though the == 0 part isn't even checked.
So, to conclude, the answer is like said in other answers to this question.
0 < 0 == 0 has a special meaning. The compiler evaluates this to two terms: 0 < 0 and 0 == 0. As with any complex boolean expressions with and between them, if the first fails then the second one isn't even checked.
Hopes this enlightens things up a bit, and I really hope that the method I used to analyse this unexpected behavior will encourage others to try the same in the future.
As other's mentioned x comparison_operator y comparison_operator z is syntactical sugar for (x comparison_operator y) and (y comparison_operator z) with the bonus that y is only evaluated once.
So your expression 0 < 0 == 0 is really (0 < 0) and (0 == 0), which evaluates to False and True which is just False.
maybe this excerpt from the docs can help:
These are the so-called “rich
comparison” methods, and are called
for comparison operators in preference
to __cmp__() below. The correspondence
between operator symbols and method
names is as follows: x<y calls
x.__lt__(y), x<=y calls x.__le__(y),
x==y calls x.__eq__(y), x!=y and x<>y
call x.__ne__(y), x>y calls
x.__gt__(y), and x>=y calls
x.__ge__(y).
A rich comparison method may return
the singleton NotImplemented if it
does not implement the operation for a
given pair of arguments. By
convention, False and True are
returned for a successful comparison.
However, these methods can return any
value, so if the comparison operator
is used in a Boolean context (e.g., in
the condition of an if statement),
Python will call bool() on the value
to determine if the result is true or
false.
There are no implied relationships
among the comparison operators. The
truth of x==y does not imply that x!=y
is false. Accordingly, when defining
__eq__(), one should also define __ne__() so that the operators will behave as expected. See the paragraph
on __hash__() for some important notes
on creating hashable objects which
support custom comparison operations
and are usable as dictionary keys.
There are no swapped-argument versions
of these methods (to be used when the
left argument does not support the
operation but the right argument
does); rather, __lt__() and __gt__()
are each other’s reflection, __le__()
and __ge__() are each other’s
reflection, and __eq__() and __ne__()
are their own reflection.
Arguments to rich comparison methods
are never coerced.
These were comparisons but since you are chaining comparisons you should know that:
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.
Here it is, in all its glory.
>>> class showme(object):
... def __init__(self, name, value):
... self.name, self.value = name, value
... def __repr__(self):
... return "<showme %s:%s>" % (self.name, self.value)
... def __cmp__(self, other):
... print "cmp(%r, %r)" % (self, other)
... if type(other) == showme:
... return cmp(self.value, other.value)
... else:
... return cmp(self.value, other)
...
>>> showme(1,0) < showme(2,0) == showme(3,0)
cmp(<showme 1:0>, <showme 2:0>)
False
>>> (showme(1,0) < showme(2,0)) == showme(3,0)
cmp(<showme 1:0>, <showme 2:0>)
cmp(<showme 3:0>, False)
True
>>> showme(1,0) < (showme(2,0) == showme(3,0))
cmp(<showme 2:0>, <showme 3:0>)
cmp(<showme 1:0>, True)
True
>>>
I'm thinking Python is doing it's weird between magic. Same as 1 < 2 < 3 means 2 is between 1 and 3.
In this case, I think it's doing [middle 0] is greater than [left 0] and equal to [right 0]. Middle 0 is not greater than left 0, so it evaluates to false.
I actually needed xor for my solution, but while thinking on it, I started wondering about the question above. What is the meaning of True == True != False?
Looking at the documentation I suppose it's True == True and True != False, but I'd like a more general and certain approach. How do I quickly get readable form of bytecode for such code. Is there an easier way to find out than both bytecode and documentation?
It's called operator chaining. Whenever you have an expression like A op1 B op2 C with op1 and op2 comparisons it is "translated" to A op1 B and B op2 C.
(Actually it does evaluate B only once).
Note: comparisons operator include in, not in, is, is not! (e.g. a is b is not None means a is b and b is not None).
If you want to look at bytecode you can use the dis module:
In [1]: import dis
In [2]: dis.dis(lambda: True == True != False)
1 0 LOAD_CONST 1 (True)
3 LOAD_CONST 1 (True)
6 DUP_TOP
7 ROT_THREE
8 COMPARE_OP 2 (==)
11 JUMP_IF_FALSE_OR_POP 21
14 LOAD_CONST 2 (False)
17 COMPARE_OP 3 (!=)
20 RETURN_VALUE
>> 21 ROT_TWO
22 POP_TOP
23 RETURN_VALUE
If you read at the bytecode you can understand that it performs operator chaining.
Given that the expression is True == True != False, which is "interpreted" as True == True and True != False it first loads the two True constants for the first operator via the LOAD_CONST bytecode. The DUP_TOP duplicates the top of the stack(this avoids re-evaluating True for the second comparison).
It performs the first comparison(COMPARE_OP) if it is false it just to the bytecode 21, otherwise it pops the top of the stack(JUMP_IF_FALSE_OR_POP). It then performs the second comparison.
To answer your general question the fastest way to find out about some feature of python is to use the quicksearch page of the documentation.
I'd also suggest to read Python's tutorial for a general introduction to the language.
I'd like to add that, since python provides an interactive environment, it is often easier to understand how some code works writing it in the interpreter and watching the results. Almost all buil-in types have the documentation available via docstrings, so doing help(some_object) should give you a lot of information.
In particular IPython provides an enhanced interactive interpreter with more user-friendly help messages/error formatting etc.)
In most languages, a == b != c parses as (a == b) != c.
Therefore, what you would expect is that True == True != False would be the same as (True == True) != False, which evaluates to True != False, which evaluates to True.
Python has a different meaning, as can be witnessed here:
>>> True != False != False
False
>>> (True != False) != False
True
In Python, a == b != c is equivalent to (a == b) and (b != c).
This means that True == True != False is equivalent to (True == True) and (True != False), which evaluates to True and True, which evaluates to True.
Coincidentally, both meanings (Python's and that of other languages) give the same result here, but one should be cautious.