acdr acdr - 6 months ago 20
Python Question

Custom chained comparisons

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).

AKS AKS
Answer

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.

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: if x is false, then x, else y

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]).

Comments