Problems with emulation of numeric types and coercion rules

ziga.seilnacht at ziga.seilnacht at
Wed Nov 2 00:20:40 CET 2005

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 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:

def convert(obj):
    if isinstance(obj, Number):
        return obj._value
        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):

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 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__
>>> print type(broken2 + number).__name__
>>> print type(working + number).__name__

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__
>>> print type(number + broken2).__name__
>>> print type(number + working).__name__

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.



if __name__ == '__main__':
    import doctest

More information about the Python-list mailing list