Add a TypeError comment to NotImplemented

When an operator dunder returns NotImplemented, if no fallback is found using the reversed dunder (or the normal dunder in a specific case, whatever), the interpreter raises a TypeError, with an informative message saying that {operator} is not supported between {type a} and {type b}, or something resembling that.

What if you could pass parameter(s) to NotImplemented, and those parameters would be passed to TypeError if and when it is raised, so that you can customize the exception ?

I’ve seen implementation where developers not caring (or not knowing) about the data model and other people’s classes that may want to operate with theirs, and wanting to tell precisely what went wrong to the user, raise TypeErrors or similar Exceptions with custom messages. I think that’s a bad practice, but there is currently no real workaround to trigger that error message while respecting the data model, the reversing of operators and whatnot.

NotImplemented is a singleton. You cannot “pass parameter(s)” to it, like you cannot pass parameter(s) to None or True.

Replacing NotImplemented with a non-singleton would have significant performance hit (in addition to breaking a lot of code which simply tests if result is NotImplemented).

3 Likes

Hmm, makes sense. The doc does advise the is test (in a roundabout way, by saying there is a single object with that value, but still).

Could we enable that customization in a different way ? For example by emitting a warning-like signal that would be caught by the part of Python that calls the method, and either raised if the operation fails or ignored if it succeeds ?

Perhaps you can use PEP 687 Enriching Exceptions with Notes for your project that need this.

I’m not sure how I could do that, since exception notes (from the PEP abstract) are meant to add info after the exception object is instanciated, whereas I would need to transmit infos before some interpreter code creates the exception object. The only code “outside” that exception being created is the user of my (in this example) library, calling mytype()+something.

And also, I noticed this practice of raising TypeErrors with explanations directly inside operator dunders in the wild, we can discuss how much it’s happening and how much it’s a problem but my point is, it’s not about any one specific project. It’s about a way of both (a) letting the Python operator procedure take place and (b) provide failure information if the procedure fails, which I think may be useful to virtually any type that overloads operators.

To take just one example, in builtin python, 3+[] raises a generic TypeError mentioning the operator +, ‘int’ and ‘list’, but the opposite []+3 raises a TypeError explaining that you can only “concatenate list (not ‘int’) to list”.
And guess what ? It does so by breaking the datamodel’s operator procedure and raising an exception directly, which is issue 103585.
I think there should be a way of elaborating like that, writing this kind of “concatenate” messages, without breaking the datamodel.

In addition to all of the above: suppose that the main operator and the fallback both result in NotImplemented, and both are “annotated” with whatever mechanism. How would you propose to combine these annotations in the resulting TypeError?

1 Like

We could either have the first one having been emitted, to make it more clear for the user, or show the two raise A from B-style, or have each comment erase the former one :person_shrugging:
I think either behavior could be acceptable, and it would be chosen depending on the chosen syntax and implementation.

I think this attempt at trying to improve the errors for NotImplemented is a bad direction to go. Essentially NotImplemented is a rudimentary mechanism for operator overloads. When there are multiple overloads, the rejected overloads typically don’t have the opportunity to run any code—let alone produce errors.

If anything, I think the effort should push NotImplemented to work more like a standard overload.

2 Likes

I think the list’s use of a customized error message shows that at least some core developers, at the beginning of Python, thought it would be useful to be more informative in what went wrong - by using fancy 4-syllable words like “concatenate”. The same way that ZeroDivisionError can take a comment, even though it’s one of the most dumb and straightforward exceptions.

But what exactly do you mean by “work like a standard overload” ? Maybe I can agree with that.

The main reason we have NotImplemented and not an exception is performance. When the coercion mechanism was overhauled in PEP 208 , we found that using an exception would slow down the mechanism too much, so we opted for a singleton instead. The TypeError you are getting if both sides of an operation fail is not generated by the types themselves. This is done as part of the operator code in Objects/abstract.c.

2 Likes