Problems with emulation of numeric types and coercion rules

Z

ziga.seilnacht

"""
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 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.
When the subclass is on the left side of the operator,
everything works as intended::
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::
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()
 
Z

ziga.seilnacht

Never mind, I forgot that class inheritance tree is a tree.
Resulting type of adding Broken and Working from previous
example would also depend on the order of operands.

Ziga
 

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

Forum statistics

Threads
473,764
Messages
2,569,566
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top