On Tue, Jan 26, 2010 at 02:56, Nick Coghlan <ncogh...@gmail.com> wrote:
> Lennart Regebro wrote:
>> On Mon, Jan 25, 2010 at 15:30, Nick Coghlan <ncogh...@gmail.com> wrote:
>>> Ah, you mean the case where both classes implement the recipe, but know
>>> nothing about each other and hence both return NotImplemented from their
>>> root comparison?
>>
>> Well, only one needs to return NotImplemented, actually.
>
> I'd like to see a test case that proved that. With two different types
> and only one of them returning NotImplemented, the recursion should
> terminate inside the one with the root comparison that can handle both
> types.

It never gets to that root comparison, as several of that types
comparisons just refer back to the type who returns NotImplemented. To
solve it you need to never refer to the other type in your
comparisons. And as far as I see that makes it impossible to implement
full comparisons with only one operator implemented.

In short, the recipe assumes you never compare with other types, but
it doesn't check for it.

Test attached (assuming this list accepts attachements).

-- 
Lennart Regebro: Python, Zope, Plone, Grok
http://regebro.wordpress.com/
+33 661 58 14 64
# By Christian Muirhead, Menno Smits and Michael Foord 2008
# WTF license
# http://voidspace.org.uk/blog

"""
``total_ordering`` and ``force_total_ordering`` are class decorators for 
Python 2.6 & Python 3.

They provides *all* the rich comparison methods on a class by defining *any*
one of '__lt__', '__gt__', '__le__', '__ge__'.

``total_ordering`` fills in all unimplemented rich comparison methods, assuming
at least one is implemented. ``__lt__`` is taken as the base comparison method
on which the others are built, but if that is not available it will be
constructed from the first one found.

``force_total_ordering`` does the same, but having taken a comparison method as
the base it fills in *all* the others - this overwrites additional comparison
methods that may be implemented, guaranteeing consistent comparison semantics.

::
    
    from total_ordering import total_ordering
    
    @total_ordering
    class Something(object):
        def __init__(self, value):
            self.value = value
        def __lt__(self, other):
            return self.value < other.value

It also works with Python 2.5, but you need to do the wrapping yourself:

::
    
    from total_ordering import total_ordering
    
    class Something(object):
        def __init__(self, value):
            self.value = value
        def __lt__(self, other):
            return self.value < other.value

    total_ordering(Something)

It would be easy to modify for it to work as a class decorator for Python
3.X and a metaclass for Python 2.X.
"""


import sys as _sys

if _sys.version_info[0] == 3:
    def _has_method(cls, name):
        for B in cls.__mro__:
            if B is object:
                continue
            if name in B.__dict__:
                return True
        return False
else:
    def _has_method(cls, name):
        for B in cls.mro():
            if B is object:
                continue
            if name in B.__dict__:
                return True
        return False



def _ordering(cls, overwrite):
    def setter(name, value):
        if overwrite or not _has_method(cls, name):
            value.__name__ = name
            setattr(cls, name, value)
            
    comparison = None
    if not _has_method(cls, '__lt__'):
        for name in 'gt le ge'.split():
            if not _has_method(cls, '__' + name + '__'):
                continue
            comparison = getattr(cls, '__' + name + '__')
            if name.endswith('e'):
                eq = lambda s, o: comparison(s, o) and comparison(o, s)
            else:
                eq = lambda s, o: not comparison(s, o) and not comparison(o, s)
            ne = lambda s, o: not eq(s, o)
            if name.startswith('l'):
                setter('__lt__', lambda s, o: comparison(s, o) and ne(s, o))
            else:
                setter('__lt__', lambda s, o: comparison(o, s) and ne(s, o))
            break
        assert comparison is not None, 'must have at least one of ge, gt, le, lt'

    setter('__ne__', lambda s, o: s < o or o < s)
    setter('__eq__', lambda s, o: not s != o)
    setter('__gt__', lambda s, o: o < s)
    setter('__ge__', lambda s, o: not (s < o))
    setter('__le__', lambda s, o: not (s > o))
    return cls


def total_ordering(cls):
    return _ordering(cls, False)

def force_total_ordering(cls):
    return _ordering(cls, True)

if __name__ == '__main__':
    @total_ordering
    class DuckTyper(object):
        def __init__(self, v):
            self.v = v
            
        def __lt__(self, other):
            return (self.v > other.v) - (self.v < other.v)
    
    @total_ordering
    class StrictComparator(object):
        def __init__(self, v):
            self.v = v

        def __lt__(self, other):
            if not isinstance(other, StrictComparator):
                return NotImplemented
            else:
                return (self.v > other.v) - (self.v < other.v)
            

    dt1 = DuckTyper(1)
    dt2 = DuckTyper(2)
    sc1 = StrictComparator(1)
    sc2 = StrictComparator(2)
    
    # Works:
    assert dt1 < sc2, 'lt'
    assert dt1 != sc2, 'ne'
    assert dt2 >= sc2, 'ge'    
    assert sc2 > dt1, 'gt'    
    assert sc1 <= dt1, 'le'
    
    # Recursion:
    assert dt1 == sc1, 'eq' 
    assert dt2 > sc1, 'gt'
    assert dt1 <= sc1, 'le'
    assert sc1 < dt2, 'lt'
    assert sc1 == dt1, 'eq'
    assert sc1 != dt2, 'ne'
    assert sc2 >= dt2, 'ge'
    print ('no errors')
    
_______________________________________________
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com

Reply via email to