[Python-ideas] Arguments to exceptions

Steven D'Aprano steve at pearwood.info
Tue Jul 4 10:03:00 EDT 2017


On Mon, Jul 03, 2017 at 01:59:02AM -0700, Ken Kundert wrote:
> I think in trying to illustrate the existing behavior I made things more 
> confusing than they needed to be.  Let me try again.

I understood you the first time :-) 

I agree that scraping the name from the NameError exception is a fragile 
hack. What I'm questioning is *how often that needs be done*.

As I see it, there are broadly two use-cases for wanting the name from 
NameError (or AttributeError, or the index from IndexError, or the key 
from KeyError -- for brevity, I will use "name" and "NameError" stand in 
for *all* these cases).

1. You are a developer reading an unexpected NameError exception, and 
now you need to debug the code and fix the bug.

In this case, just reading the error message is sufficient. There's no 
need to programmatically extract the name.

2. You have a `try...except` block and you've just caught NameError and 
want to handle it programmatically.

In that second case, needing to extract the name from the exception is a 
code smell. A *strong* code smell -- it suggests that you're doing too 
much in the try... block. You should already know which name lookup 
failed, and so extracting the name from the exception is redundant:

try:
    unicode
except NameError:
    # Python 2/3 compatibility
    unicode = str

What other name could it be?

I believe that if you are dealing with a NameError where you want to 
programmatically deal with a missing name, but you don't know what that 
name is, you're already in deep, deep trouble and the fact that you have 
to scrape the error message for the name is the least of your problems:

try:
    # Feature detection for Python 2/3 compatibility.
    unicode
    ascii
    reduce
    callable
except NameError as err:
    name = extract_name(err)  # somehow...
    if name == 'unicode':
        unicode = str
    elif name == 'ascii':
        ascii = ... 
    elif name == 'reduce':
        from functools import reduce
    elif name == 'callable':
        def callable(obj): ...

I trust that the problem with this is obvious. The only way to safely 
write this code is to test for each name individually, in which case 
we're back to point 2 above where you know what name failed and you 
don't need to extract it at all.

It is my hand-wavy estimate that these two use-cases cover about 95% of 
uses for the name. We might quibble over percentages, but I think we 
should agree that whatever the number is, it is a great majority.

Any other use-cases, like your example of translating error messages, or 
suggesting "Did you mean...?" alternatives, are fairly niche uses.

So my position is that given the use-cases for programmatically 
extracting the name from NameError fall into a quite small niche, this 
feature is a "Nice To Have" not a "Must Have". It seems to me that the 
benefit is quite marginal, and so any more than the most trivial cost to 
implement this is likely to be too much for the benefit gained.

I don't just mean the effort of implementing your suggested change. I 
mean, if there is even a small chance that in the future people 
will expect me (or require me!) to write code like this:

    raise NameError('foo', template='missing "{}"')

instead of

    raise NameError('missing "foo"')

then the cost of this new feature is more than the benefit to me, and I 
don't want it.

I see little benefit and a small cost (more of a nuisance than a major 
drama, but a nusiance is still a nagative). I wouldn't use the new style 
if it were available (why should I bother?) and I'd be annoyed if I were 
forced to use it.

Contrast that to OSError, where the ability to extract the errno and 
errstr separately are *very* useful. When you catch an OSError or 
IOError, you typically have *no idea* what the underlying errno is, it 
could be one of many. I don't object to writing this:

    raise OSError(errno, 'message')

because I see real benefit.


[...]
> The above is an example. It is a bit contrived. I simply wanted to illustrate 
> the basic issue in a few lines of code. However, my intent was also to 
> illustrate what I see as a basic philosophical problem in the way we approach 
> exceptions in Python:
> 
>     It is a nice convenience that an error message is provided by the source of 
>     the error, but it should not have the final say on the matter.  
>     Fundamentally, the code that actually presents the error to the user is in 
>     a better position to produce a message that is meaningful to the user.  So, 
>     when we raise exceptions, not only should we provide a convenient human 
>     readable error message, we should anticipate that the exception handler may 
>     need to reformat or reinterpret the exception and provide it with what it 
>     need to do so.

That argument makes a certain amount of sense, but its still a niche 
use-case. In *general*, we don't do our users any favours by 
reinterpreting standard error messages into something that they can't 
google, so even if this were useful, I can't see it becoming used in a 
widespread manner.


[...]
> What I am hoping to do with this proposal is to get the Python developer 
> community to see that:
> 1. The code that handles the exception benefits from having access to the 
>    components of the error message.

I don't think that's generally true.


>    In the least it can present the message to 
>    the user is the best possible way. Perhaps that means enforcing a particular 
>    style, or presenting it in the user's native language, or perhaps it means 
>    providing additional related information as in the example above.

And I think that's actually counter-productive, at least in general, 
although I'm willing to accept that there may be situations were it is 
helpful.


> 2. The current approach to exceptions follows the opposite philosophy, 
>    suggesting that the best place to construct the error message is at the 
>    source of the error.

What else understands the error better than the source of the error?


>    It inadvertently puts obstacles in place that make it 
>    difficult to customize the message in the handler.
>
> 3. Changing the approach in the BaseException class to provide the best of both 
>    approaches provides considerable value and is both trivial and backward 
>    compatible.

I'll discuss your suggested API in a followup email.



-- 
Steve


More information about the Python-ideas mailing list