On Sat, Jun 19, 2021 at 03:30:05AM -0000, wyz2...@163.com wrote:

> Python raises TypeError when NotImplemented is returned from __add__, 
> __invert__ etc. and __radd__ etc. aren't available.

Roughly correct. It's more complex than that.


> However, this disallows the customization of error messages. 

Given a binary operator like `+`, there are two objects involved. Which 
one do you think should get to customize the error message if neither 
object handles the plus operator?

We can't customize error messages for syntax errors, attribute errors, 
import errors, value errors, etc. I'm not sure that type errors are 
special enough that they need to be customized.


> For example, in Python 3.8, __float__ etc. were removed from complex 
> to allow methods like __rfloat__.

Are you sure about that? This is in 3.9:

    >>> complex.__float__
    <slot wrapper '__float__' of 'complex' objects>

    >>> complex.__rfloat__
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: type object 'complex' has no attribute '__rfloat__'


> But this makes abs(z) undiscoverable for many users.

The set of people who know about complex numbers but not about using 
`abs(z)` to get the magnitude of a complex number is surely very small.


> The original error message is very helpful.

We have to go all the way back to Python 2.5 to get a hint to use 
abs(z):

    >>> float(1+2j)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: can't convert complex to float; use abs(z)

I don't think Python 3 has ever given such a hint: the oldest version I 
have installed, 3.2, doesn't.

In any case, if we really think this is important, we could special case 
complex numbers. But I don't think it is important.

Remember that it isn't mandatory for error messages to explain 
everything the caller needs to know about a data type. People are 
permitted to read the documentation and even search the internet.


> I suggest to make a way for this usage. Maybe NotImplementedType can 
> accept *args, and NotImplemented can be callable, equal to __init__:

What would the result of calling NotImplemented be?

If it is the immutable NotImplemented singleton then supplying any 
arguments would be a no-op. They would just be ignored.

If it returns something else, then it wouldn't be the NotImplemented 
singleton and that would become the operator's result.

If we made NotImplemented mutable, that would lead to difficulty with 
multi-threaded code. One thread could modify the object, changing the 
error message, before another thread made use of that message. Fixing 
that would require locks, which would have a big performance impact. I 
would expect that it would slow down almost every operator.

If we changed NotImplemented to no longer be a singleton, that would 
break code that checks for it with the `is` operator, which is one of 
the two main uses for `is` (the other is checking for None).

Making this work would be a big change for a very small benefit.

*If* we thought that being able to customize TypeErrors was important, 
it would, I think, be easier to add a new protocol to operators. After 
going through the whole left-and-right operand dance and calling the 
appropriate methods, if neither operand supported the operator, then the 
interpreter could look at each operand in turn for a new dunder, and 
then call that to get the customized error message.

The API for that dunder would be complicated, but here is my quick 
design. There are two cases, for unary functions and operators and for 
binary operators.

The unary operator case: function or operator which calls `__OP__` would 
then look for a new dunder `__OP_error__` containing the error message. 
So:

    complex.__float_error__ = 'use abs(z) instead'


The binary operator case: the operator which calls `__OP__` would then 
look for a new *callable* dunder that returns an error message.

For example, matrices don't define division, so we could do:

    class Matrix:
        def __true_division_error__(self, type_of_other):
            if type_of_other is Matrix:
                # Matrix / Matrix not permitted
                return "matrix division not defined; try multiplying by the 
inverse matrix"

If the `__OP_error__` dunder returns None or NotImplemented, or doesn't 
exist, no additional message is appended to the TypeError.

This is pretty complicated, for not a lot of benefit IMO. I think people 
should just learn to read the docs.


-- 
Steve
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/ECC3NPAPC43GYDA46PELOL3UGRNWAYNW/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to