""" I am trying to write some classes representing the quaternion number. I wrote a base class, which implements only the numerical interface, and a few subclasses, which provide methods for their specific domain.
Since the operator methods will be the same for all these classes, the base class operator methods don't explicitly return an instance of this base class, but rather an instance of the class that called them (ie 'return self.__class__(*args)' not 'return Quaternion(*args)') Documentation at http://docs.python.org/ref/coercion-rules.html says: Below, __op__() and __rop__() are used to signify the generic method names corresponding to an operator; __iop__() is used for the corresponding in-place operator. For example, for the operator `+', __add__() and __radd__() are used for the left and right variant of the binary operator, and __iadd__() for the in-place variant. 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, the right operand's __rop__() method is tried before the left operand's __op__() method. This is done so that a subclass can completely override binary operators. Otherwise, the left operand's __op__ method would always accept the right operand: when an instance of a given class is expected, an instance of a subclass of that class is always acceptable. So I thought my plan would work. But it shows that even if the right operand is a subclass of left operand, its __rop__() method is called first _only_ when it overwrites the parent's method. If the method is inherited or just copied from its parent, the rule is ignored. Here is a simplified example: """ # file: number.py def convert(obj): if isinstance(obj, Number): return obj._value try: f = float(obj) except (TypeError, ValueError): return NotImplemented if f == obj: return f return NotImplemented class Number(object): def __init__(self, value=0.): value = float(value) self._value = value def __add__(self, other): """ Return sum of two real numbers. Returns an instance of self.__class__ so that subclasses would't have to overwrite this method when just extending the base class' interface. """ other = convert(other) if other is NotImplemented: return NotImplemented return self.__class__(self._value + other) __radd__ = __add__ # other methods class Broken(Number): pass class StillBroken(Number): __add__ = __radd__ = Number.__add__ class Working(Number): def __add__(self, other): return Number.__add__(self, other) __radd__ = __add__ __doc__ = \ """ If I now open the python interpreter:: >python Python 2.4.2 (#67, Sep 28 2005, 12:41:11) [MSC v.1310 32 bit (Intel)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> from number import * >>> number = Number() >>> broken1 = Broken() >>> broken2 = StillBroken() >>> working = Working() When the subclass is on the left side of the operator, everything works as intended:: >>> print type(broken1 + number).__name__ Broken >>> print type(broken2 + number).__name__ StillBroken >>> print type(working + number).__name__ Working But when the sublass is on the right side of the operator, only the subclass that has owerwritten the operator method gets called first:: >>> print type(number + broken1).__name__ Number >>> print type(number + broken2).__name__ Number >>> print type(number + working).__name__ Working According to the coercion rule, the subclass should allways be called first. Is this a bug (either in documentation or in python), or should I stop trying to 'upcast' the return value? I did find a solution to this problem, but it isn't pretty and I'm not the only one using this method. Also if this is a bug could this mail be used as a bug report? Thanks in advance. Ziga """ if __name__ == '__main__': import doctest doctest.testmod() -- http://mail.python.org/mailman/listinfo/python-list