[Python-Dev] Decimal & returning NotImplemented (or not)
Nick Coghlan
ncoghlan at iinet.net.au
Wed Mar 2 11:23:34 CET 2005
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 | ncoghlan at email.com | Brisbane, Australia
---------------------------------------------------------------
http://boredomandlaziness.skystorm.net
More information about the Python-Dev
mailing list