Reputation: 4726
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).
Upvotes: 17
Views: 617
Reputation: 19861
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])
.
Upvotes: 8
Reputation: 5822
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])
Upvotes: 3
Reputation: 13232
You should return a boolean value from your comparison methods.
To cite the documentation for "rich comparison" methods:
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.
To break it down for this case:
exp1 = low > high
print(exp1)
print(bool(exp1))
exp2 = high > low
print(exp2)
print(bool(exp2))
Will give you
CompareList([False])
True
CompareList([True])
True
Now we do the last operation and print out the result
print(exp1 and exp2)
Because both values evaluate to True
you'll get
CompareList([True])
Upvotes: 1