Python allows expressions like x > y > z
, which, according to the docs, is equivalent to (x > y) and (y > z)
except y
is only evaluated once. (https://docs.python.org/3/reference/expressions.html)
However, this seems to break if I customize comparison functions. E.g. suppose I have the following class: (Apologies for the large block, but once you read the __eq__
method, the rest is trivial.)
class CompareList(list):
def __repr__(self):
return "CompareList([" + ",".join(str(x) for x in self) + "])"
def __eq__(self, other):
if isinstance(other, list):
return CompareList(self[idx] == other[idx] for idx in xrange(len(self)))
else:
return CompareList(x == other for x in self)
def __ne__(self, other):
if isinstance(other, list):
return CompareList(self[idx] != other[idx] for idx in xrange(len(self)))
else:
return CompareList(x != other for x in self)
def __gt__(self, other):
if isinstance(other, list):
return CompareList(self[idx] > other[idx] for idx in xrange(len(self)))
else:
return CompareList(x > other for x in self)
def __ge__(self, other):
if isinstance(other, list):
return CompareList(self[idx] >= other[idx] for idx in xrange(len(self)))
else:
return CompareList(x >= other for x in self)
def __lt__(self, other):
if isinstance(other, list):
return CompareList(self[idx] < other[idx] for idx in xrange(len(self)))
else:
return CompareList(x < other for x in self)
def __le__(self, other):
if isinstance(other, list):
return CompareList(self[idx] <= other[idx] for idx in xrange(len(self)))
else:
return CompareList(x <= other for x in self)
Now I can do fun stuff like CompareList([10, 5]) > CompareList([5, 10])
and it will correctly return CompareList([True,False])
However, chaining these operations doesn't work nicely:
low = CompareList([1])
high = CompareList([2])
print(low > high > low) # returns CompareList([True])
Why not? What happens under the hood here? I know it isn't equivalent to (low > high) > low
= (False > low)
(because that would return False). It could be low > (high > low)
but that wouldn't make sense in terms of operator precedence (normally left-to-right).
Python supports chaining of comparison operators, which means if we wanted to find out if b lies between a and c we can do a < b < c , making code super-intuitive. Python evaluates such expressions like how we do in mathematics. which means a < b < c is evaluated as (a < b) and (b < c) .
Javascript does not support the chained comparison syntax used in mathematics: 1 < 2 < 3 // 1 is less than 2 which is less than 3.
A comparison operator in python, also called python relational operator, compares the values of two operands and returns True or False based on whether the condition is met.
Python allows expressions like
x > y > z
, which, according to the docs, is equivalent to(x > y) and (y > z)
excepty
is only evaluated once.
According to this, low > high > low
will be equivalent to (low > high) and (high > low)
.
>>> x = low > high # CompareList([False])
>>> y = high > low # CompareList([True])
>>> x and y
CompareList([True])
More from the documentation on x and y:
x and y
: ifx
is false, thenx
, elsey
In the above case:
>>> x is False
False
>>> x if x is False else y # x and y
CompareList([True])
so when you do x and y
it returns the y
which is CompareList([True])
.
The other answers are right, but I wanted to address the actual lack of implementation for this problem, because, as I believe, what the OP would like to get as a result from low > high > low
is a CompareList([False])
.
Indeed, the low > high > low
evaluates to (low > high) and (high > low)
and since CompareList([False]) is False
evaluates to False
(which means that it is True
), then the second operand of and
operator gets evaluated and returned (as it also evaluates to True
).
The key to implementing the chained comparison is to override the and
boolean operator along __gt__
and __lt__
.
Unfortunately, there is no way to do this, and probably won't be. The PEP 335 - Overloadable Boolean Operators
proposal was rejected by Guido, but he might consider making chained comparisons like a < b < c overloadable [1].
Unless that moment, there is no way to get your example to work as expected when using chained comparisons.
The only way to achieve the correct result is by overriding the __and__
method and writing your comparisons like this:
def CompareList(list):
...
def __and__(self, other):
if isinstance(other, list):
return CompareList(self[idx] and other[idx] for idx in range(len(self)))
else:
return CompareList(x and other for x in self)
Then, by writing in the form below, you'll get the correct answer:
low = CompareList([1, 2])
high = CompareList([2, 2])
print((low >= high) & (high >= low)) # returns CompareList([False, True])
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