That's not surprising. You're measuring the wrong things. If you read
what I wrote, you'll see that I'm talking about Fraction.__gt__ being
slower (as it is defined in terms of Fraction.__eq__ and
Fraction.__lt__) using when my 'totally_ordered' decorator.
I haven't done any tests but as Fraction.__gt__ calls *both*
Fraction.__eq__ and Fraction.__lt__ it is obvious that it is going to be
roughly twice as slow.
What's obvious to you and what's obvious to the Python VM are not
necessarily the same thing. I believe you are worrying about the wrong
thing. (BTW, I think your earlier decorator had a bug, in that it failed
to define __ne__ but then called "self != other".) My tests suggest that
relying on __cmp__ is nearly three times *slower* than your decorated
class, and around four times slower than defining all the rich
comparisons directly:
$ python comparisons.py
Testing FractionCmp... 37.4376080036
Testing FractionRichCmpDirect... 9.83379387856
Testing FractionRichCmpIndirect... 16.152534008
Testing FractionDecoratored... 13.2626030445
Test code follows. If I've made an error, please let me know.
[comparisons.py]
from __future__ import division
def totally_ordered(cls):
# Define total ordering in terms of __eq__ and __lt__ only.
if not hasattr(cls, '__ne__'):
def ne(self, other):
return not self == other
cls.__ne__ = ne
if not hasattr(cls, '__gt__'):
def gt(self, other):
return not (self < other or self == other)
cls.__gt__ = gt
if not hasattr(cls, '__ge__'):
def ge(self, other):
return not (self < other)
cls.__ge__ = ge
if not hasattr(cls, '__le__'):
def le(self, other):
return (self < other or self == other)
cls.__le__ = le
return cls
class AbstractFraction:
def __init__(self, num, den=1):
if self.__class__ is AbstractFraction:
raise TypeError("abstract base class, do not instantiate")
assert den > 0, "denomintator must be > 0"
self.num = num
self.den = den
def __float__(self):
return self.num/self.den
class FractionCmp(AbstractFraction):
def __cmp__(self, other):
return cmp(self.num*other.den, self.den*other.num)
class FractionRichCmpDirect(AbstractFraction):
def __eq__(self, other):
return (self.num*other.den) == (self.den*other.num)
def __ne__(self, other):
return (self.num*other.den) != (self.den*other.num)
def __lt__(self, other):
return (self.num*other.den) < (self.den*other.num)
def __le__(self, other):
return (self.num*other.den) <= (self.den*other.num)
def __gt__(self, other):
return (self.num*other.den) > (self.den*other.num)
def __ge__(self, other):
return (self.num*other.den) >= (self.den*other.num)
class FractionRichCmpIndirect(AbstractFraction):
def __cmp__(self, other):
return cmp(self.num*other.den, self.den*other.num)
def __eq__(self, other):
return self.__cmp__(other) == 0
def __ne__(self, other):
return self.__cmp__(other) != 0
def __lt__(self, other):
return self.__cmp__(other) < 0
def __le__(self, other):
return self.__cmp__(other) <= 0
def __gt__(self, other):
return self.__cmp__(other) > 0
def __ge__(self, other):
return self.__cmp__(other) >= 0
class FractionDecoratored(AbstractFraction):
def __eq__(self, other):
return self.num*other.den == self.den*other.num
def __lt__(self, other):
return self.num*other.den < self.den*other.num
FractionDecoratored = totally_ordered(FractionDecoratored)
def test_suite(small, big):
assert small < big
assert small <= big
assert not (small > big)
assert not (small >= big)
assert small != big
assert not (small == big)
from timeit import Timer
test = 'test_suite(p, q)'
setup = '''from __main__ import %s as frac
from __main__ import test_suite
p = frac(1, 2)
q = frac(4, 5)
assert float(p) < float(q)'''
for cls in [FractionCmp, FractionRichCmpDirect, FractionRichCmpIndirect,
FractionDecoratored]:
t = Timer(test, setup % cls.__name__)
print "Testing %s..." % cls.__name__,
best = min(t.repeat())
print best
[end comparisons.py]
--
Steven