total_ordering behaviour

R

risboo6909

Hello all,

I've noticed some strange behaviour of functools.total_ordering
decorator, at least it seems strange to me.

Let's consider code snippet below

import functools

@functools.total_ordering
class MyComparableType(object):

def __init__(self, value, ref):
self.value = value
self.ref = ref

def __eq__(self, other):
# compare two MyComparableType objects or MyComparableType
object and some number
if type(other) == MyComparableType:
return self.value == other.value
return self.value == other

def __gt__(self, other):
# compare two MyComparableType objects or MyComparableType
object and some number
if type(other) == MyComparableType:
return self.value > other.value
return self.value > other

foo = MyComparableType(10, None)
bar = MyComparableType(20, foo)

print foo < bar # works ok, True
print foo > bar # works ok, False
print foo > 5 # works ok, True

print foo < 8 # error! we jump into infinite recursion and
eventually get stack overflow error

It works fine in most cases but crashes when I try to check foo < 8.

As I understand the reason why this happens is because of this code in
functools.py, total_ordering decorator, which adds missing comparisons
definitions

......

'__gt__': [('__lt__', lambda self, other: other > self),
('__ge__', lambda self, other: not other > self),
('__le__', lambda self, other: not self > other)],

.......

and python coercion rules say:

For objects x and y, first x.__op__(y) is tried. If this is not
implemented or returns NotImplemented, y.__rop__(x) is tried. If this
is also not implemented or returns NotImplemented, a TypeError
exception is raised. But see the following exception:

Exception to the previous item: if the left operand is an instance of
a built-in type or a new-style class, and the right operand is an
instance of a proper subclass of that type or class and overrides the
base’s __rop__() method, the right operand’s __rop__() method is tried
before the left operand’s __op__() method.

So it behaves like it should according to this rule and produces stack
overflow because of the infinite recursion calls in the last
comparison (foo < 8).

There is a way to fix this by replacing missing comparisons
definitions in total_ordering to be like this:

'__gt__': [('__lt__', lambda self, other: not self > other and not
self == other),
('__ge__', lambda self, other: self > other or self ==
other ),
('__le__', lambda self, other: not self > other)],

then script above will work perfectly well in all the cases and there
is just one more check added in two of three definitions.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,071
Latest member
MetabolicSolutionsKeto

Latest Threads

Top