What is the associativity of comparison operators in Python? It is straightforward for three comparisons, but for more than that, I'm not sure how it does it. They don't seem to be right- or left-associative.
For example:
>>> 7410 >= 8690 <= -4538 < 9319 > -7092
False
>>> (((7410 >= 8690) <= -4538) < 9319) > -7092
True
So, not left-associative.
>>> 81037572 > -2025 < -4722 < 6493
False
>>> (81037572 > (-2025 < (-4722 < 6493)))
True
So it's not right-associative either.
I have seen some places that they are 'chained', but how does that work with four or more comparisons?
Chained comparisons are expanded with and, so:
a <= b <= c
becomes:
a <= b and b <= c
(b is only evaluated once, though). This is explained in the language reference on comparisons.
Note that lazy evaluation means that if a > b, the result is False and b is never compared to c.
Your versions with parentheses are completely different; a <= (b <= c) will evaluate b <= c then compare a to the result of that, and isn't involved at all, so it's not meaningful to compare the results to determine associativity.
python short-circits boolean tests from left to right:
7410>=8690<=-4538<9319>-7092 -> False
7410>=8690 is False. that's it. the rest of the tests is not preformed.
note that
True == 1
False == 0
are both True and apply when you compare the booleans with integers. so when you surround the statement with brackets you force python to do all the tests; in detail:
(((7410>=8690)<=-4538)<9319)>-7092
False <=-4538
False <9319
True >-7092
True
You are making an error with types, when you write 81037572>-2025 then the system thinks of this as True or False and associates it with 1 and 0. It therefore then gives you a comparison with those binary numbers.
Related
So I just started learning Python, and while I was just playing with the shell I came around a weird thing.
print(5!=6==True,"=",True) #Gave False=True
print(5!=6==False,"=",True) #Gave False=True
print(5!=6!=True,"=",True) #Gave True=True
Can anyone explain to me why does this happen
the expression a op1 b op2 c is equivalent to (a op1 b) and (b op2 c)
https://docs.python.org/3/reference/expressions.html#comparisons
https://www.geeksforgeeks.org/chaining-comparison-operators-python/
mainly used to enable easy declaration of constraints such as min < x < max
What's interesting about this is it isn't about regular operator precedence. Adding parentheses doesn't give the same result:
>>> 5!=6==True
False
>>> (5!=6)==True
True
>>> 5!=(6==True)
True
The first one is actually parsed as a chained comparison:
5!=6 and 6==True
which is True and False (result False).
The more natural use of chained comparisons is:
x = 5
if 0 < x < 10: # True
which is parsed as:
0 < x and x < 10
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.
The Python documentation for operator precedence states:
Operators in the same box group left to right (except for
comparisons, including tests, which all have the same precedence and
chain from left to right — see section Comparisons...)
What does this mean? Specifically:
"Operators in the same box group left to right (except for
comparisons...)" -- do comparisons not group left to right?
If comparisons do not group left to right, what do they do instead? Do they "chain" as opposed to "group"?
If comparisons "chain" rather than "group", what is the difference between "chaining" and "grouping"?
What would be some examples to demonstrate that the comparison operators chain from left to right rather than from right to left?
Grouping (this is what non-comparison operators do):
a + b + c means (a + b) + c
Chaining (this is what comparison operators do):
a < b < c means (a < b) and (b < c)
Grouping left to right (this is the way things are grouped):
5 - 2 - 1 means (5 - 2) - 1 == 2
as opposed to grouping right to left (this would produce a different result):
5 - (2 - 1) == 4
Chaining left to right
Chaining is left to right, so in a < b < c, the expression a < b is evaluated before b < c, and if a < b is falsey, b < c is not evaluated.
(2 < 1 < f()) gives the value False without calling the function f, because 2 < 1 evaluates to false, so the second comparison does not need to be performed.
f() > 1 > g() calls f() in order to evaluate the first comparison, and depending on the result, it might or might not need to evaluate the second condition, which requires calling g().
NB. Each operand is evaluated at most once. So in the expression 1 < f() < 2, the function f() is only called once, and the value it gives is used in both comparisons (if necessary).
https://en.wikipedia.org/wiki/Short-circuit_evaluation
In fact, the chain behavior is not so obvious.
a == b == c
although one would expect this to be converted to
a == b and b == c
it is in fact converted into somthing similar to
b == c if a == b else False
which is a bit confusing if one tries to override the behavior of the comparison operators and chain them.
According to the doc and this tutorial,
cmp() returns -1 if x < y
and
cmp() returns 0 if x == y
and
cmp() returns 1 if x > y
The tutorial also said that
cmp() returns the sign of the difference of two numbers
I don't really get what sign of the difference of two numbers means. Doesn't that mean that it returns a value when the sign of numbers aren't equal? Since...
cmp(80, 100) : -1 # both have positive sign.
cmp(180, 100) : 1 # both also have positive sign.
cmp(-80, 100) : -1
cmp(80, -100) : 1
**Note: code from the tutorial.*
Despite my confusion in sign differences, I can't really think of why do we need a built-in function to return a value of -1 when x < y.
Isn't the function cmp( ) easily implemented ? Is there any reason why Python creators keep cmp( ) function, or is there any hidden usage of this Python's cmp( ) function ?
Why cmp( ) is useful?
It isn't very useful, which is why it was deprecated (the builtin cmp is gone and builtin sorts no longer accept one in Python 3). Rich comparison methods supplanted it:
object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
object.__ge__(self, other)
This allows the < symbol (and other symbols) to be overloaded comparison operators, enabling, for example, subset and superset comparisons of set objects.
>>> set('abc') < set('cba')
False
>>> set('abc') <= set('cba')
True
>>> set('abc') == set('cba')
True
>>> set('abc') >= set('cba')
True
>>> set('abc') > set('cba')
False
while it could enable the above, cmp wouldn't allow the following:
>>> set('abc') == set('bcd')
False
>>> set('abc') >= set('bcd')
False
>>> set('abc') <= set('bcd')
False
Toy usage for cmp
Here's an interesting usage which uses its result as an index (it returns -1 if the first is less than the second, 0 if equal, and 1 if greater than):
def cmp_to_symbol(val, other_val):
'''returns the symbol representing the relationship between two values'''
return '=><'[cmp(val, other_val)]
>>> cmp_to_symbol(0, 1)
'<'
>>> cmp_to_symbol(1, 1)
'='
>>> cmp_to_symbol(1, 0)
'>'
According to the docs, you should treat cmp as if it wasn't there:
https://docs.python.org/3/whatsnew/3.0.html#ordering-comparisons
cmp removed, equivalent operation
But you can use this as the equivalent:
(a > b) - (a < b)
in our little toy function, that's this:
def cmp_to_symbol(val, other_val):
'''returns the symbol representing the relationship between two values'''
return '=><'[(val > other_val) - (val < other_val)]
I don't really get what does it mean sign of the difference of two numbers.
This means: take the difference, and then the sign of that difference. For example, if x and y are two numbers:
x < y => x - y < 0 and the function returns -1.
x == y => x - y == 0 and the function returns 0.
x > y => x - y > 0 and the function returns 1.
For more information on three-way comparisons, see Wikipedia.
Trivalued comparators are very useful when sorting. You don't just want to know whether two elements are equal; you also want to know their relative order so that you know how to rearrange them to move closer to a sorted list. This is why C (strcmp) and Perl (cmp) both have similar operations (in those cases for strings, but it's the same idea).
For sorting sequences of items. When you are sorting a list of items you only need to know one item is greater or less than another item.
More info here: http://wiki.python.org/moin/HowTo/Sorting/#The_Old_Way_Using_the_cmp_Parameter
Another use case: Finding the sign (- / +) of a number
If you want to find out, what the sign (+/-) of a number is, you can easily use 0 as the second argument to the cmp function
cmp(-123, 0) #returns -1
cmp( 123, 0) #returns 1