class LarchTree:
...
def __gt__(self, other):
# A lot of code to traverse the tree
def __le__(self):
return not self > other
However, if I'm thinking correctly, this is a bad idea. The
reasoning being that > and <= are not required to be each others
opposite for arbitrary value types, and may not even both be
supplied by the contained objects.
If a LarchTree user stores objects that don't support __gt__,
will he have a legitimate complaint when using LarchTree.__le__
results in an attribute error?
--
Neil Cerutti
I'm not sure that an AttributeError is the right exception to raise, but
in general I'd say that if the contained objects don't support "normal"
comparisons (that is, if < and >= aren't opposites, etc.) then all bets
are off. "Behaviour is undefined" time.
BTW, what is a LarchTree? Googling just brings up the actual botanical
tree, a type of conifer.
http://en.wikipedia.org/wiki/Larch
If you haven't already done so, you should consider emulating the
behaviour of lists, and only raise an error if it is absolutely necessary:
>>> L1 = [35, 23+42j]
>>> L2 = [37, 18]
>>> L1 <= L2
True
>>> L1 = [23+42j, 35]
>>> L1 <= L2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: no ordering relation is defined for complex numbers
--
Steven
I wasn't going to raise the exception, I was thinking about calling a
function that raised the exception.
Assume for a moment that list's __le__ were implemented as above.
class BadGT(object):
def __gt__(self, o):
raise RuntimeError("I just hate this operation")
def __le__(self, o):
return id(self) <= id(other)
>>> x = [BadGT(), BadGT()]
>>> x <= []
If lists implementation of __le__ calls __gt__ on its constituents
*then* it'll be a RuntimeError.
I've tested list, and it safely compares lists containing BadGT
objects, as along I don't call __gt__ myself. So I was contemplating
implementing a phalanx of tests along these lines, which the current
version of my container will surely fail.
> BTW, what is a LarchTree? Googling just brings up the actual botanical
> tree, a type of conifer.
>
> http://en.wikipedia.org/wiki/Larch
It was a Monty Python reference. I'm actually still fiddling around
with a doubly-linked list implementation. It's kinda embarrassing so I
hid that fact, but actually I'm learning a bunch of Python and getting
design practice from this exercise (it turns out linked lists have
keys!).
--
Neil Cerutti
And yet, by the same reasoning, using > and <= for list and tuple is
also a "bad idea".
> If a LarchTree user stores objects that don't support __gt__,
> will he have a legitimate complaint when using LarchTree.__le__
> results in an attribute error?
No.
Carl Banks
My reasoning is (I hope) that the container ought to support every
comparison operation supported by the contained objects. This can be
ensured by being careful in the implementation.
--
Neil Cerutti
> My reasoning is (I hope) that the container ought to support every
> comparison operation supported by the contained objects. This can be
> ensured by being careful in the implementation.
>
If you're traversing two containers in parallel to compare them then
you could do something like:
class LarchTree:
...
def compare(self, other, cmp):
# Same code as your original __gt__ but
# with cmp(x, y) instead of x > y
def __lt__(self, other):
return self.compare(other, operator.lt)
def __gt__(self, other):
return self.compare(other, operator.gt)
# etc...
This way the LarchTree object doesn't interpret the comparison
operators, just passes them on to its elements.
--
Arnaud
I see what you're asking now. Yep, probably a bad idea to use that
shortcut.
Carl Banks
I put together a test suite, and discovered that the built-in
list assumes that all contained object support __eq__, i.e., it
may call __eq__ while performing some other comparison, e.g.,
__gt__. This is contrary to the rule I was contemplating for
sequence comparisons in this thread.
>>> class Evil:
... def __init__(self, i):
... self.i = i
... def __eq__(self, o):
... raise RuntimeError
... def __gt__(self, o):
... if isinstance(o, Evil):
... return self.i > o.i
... return NotImplemented
...
>>> [Evil(1)] > [Evil(0)]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in __eq__
RuntimeError
This prompted inspection of listobject.c. After trying some
short-cuts for __ne__ and __eq__, list_richcompare iterates
through both lists until the first non-equal elements are found,
and then performs the correct comparison, or simply returns,
depending on if the lists were the same length.
Otherwise, the built-in list rich comparison follows the advice
posted in this thread, i.e., it uses only the comparison
operation being implemented to compare contained objects.
When implementing a mutable sequence in Python, the rule of thumb
seems to be "do as lists do," so as of the Python's current
implementation, there's a small limit on the perversity level of
the rich comparison operations of types contained in lists.
--
Neil Cerutti