Neil Schemenauer wrote:
 IMO, Decimal should
be returning NotImplemented instead of raising TypeError.

I agree, but I'm also interested in the fact that nobody commented on this before Python 2.4 went out. I read the reference page on numeric coercion more times than I care to count, and still didn't register that there might be an issue with raising the TypeError - and that's only me. Relative to people like Raymond and Facundo, I spent comparatively little time working on Decimal :)


I think the problem arose because the natural thing to do in Python code is to push the type inspection for operations into a common method, and have that method raise TypeError if the other argument isn't acceptable.

Trying to convert that exception to a 'NotImplemented' return value is a pain (it's less of a pain in C, since you're already checking for error returns instead of using exceptions). The error return idiom is really unnatural in Python code. Mixing the error return for the special methods with raising an exception for non-special methods is exceedingly ugly (particularly for Decimal, since operations through Context objects should *always* raise a TypeError for bad arguments. If the special methods return NotImplemented instead of raising an exception, then Context has to convert those returns to TypeErrors).

Additionally, returning NotImplemented means that direct calls to special methods may not raise an exception in response to a bad argument. Compare:

Py> 1 .__add__("")
NotImplemented
Py> "".__add__(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: cannot concatenate 'str' and 'int' objects

Hmm, perhaps a decorator would help here. Something like:

class OperatorTypeError(TypeError): pass

class operatormethod(object):
    def __init__(self, method):
        self._method = method
        self._obj = None
    def __get__(self, obj, type=None):
        self._obj = obj
        return self
    def __call__(*args, **kwds):
        self = args[0]
        obj = self._obj
        try:
            if obj is None:
                return self._method(*args[1:], **kwds)
            return self._method(obj, *args[1:], **kwds)
        except OperatorTypeError:
            return NotImplemented

Then do:

Py> class C:
...     def _add(self, other):
...         raise OperatorTypeError
...     __add__ = operatormethod(_add)
...     __radd__ = __add__
...
Py> C()._add(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 3, in _add
__main__.OperatorTypeError
Py> C().__add__(1)
NotImplemented
Py> C() + 1
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: unsupported operand type(s) for +: 'instance' and 'int'
Py> 1 + C()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: unsupported operand type(s) for +: 'int' and 'instance'

That makes it easy to write natural Python code (raising a specific subclass of type error), while still returning NotImplemented so the operator coercion works correctly.

>  That
> could be considered a bug but I'd file it under new functionality
> for the purposes of backporting (i.e. fix it in 2.5 only).

Given that I'm already ambivalent about changing this for 2.4, it won't take much to convince me that this should be left to 2.5.

Particularly if we actually try to find a way to make it easier to 'do the right thing', rather than just changing Decimal.

Cheers,
Nick.

--
Nick Coghlan   |   [EMAIL PROTECTED]   |   Brisbane, Australia
---------------------------------------------------------------
            http://boredomandlaziness.skystorm.net
_______________________________________________
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