[Python-ideas] Arguments to exceptions

Jeff Walker jeff.walker00 at yandex.com
Mon Jul 3 15:46:14 EDT 2017


Paul,
     I think you are fixating too much on Ken's example. I think I understand what he
is saying and I agree with him. It is a problem I struggle with routinely. It occurs in
 the following situations:

1. You are handling an exception that you are not raising. This could be because
    Python itself is raising the exception, as in Ken's example, or it could be raised
    by some package you did not write.
2. You need to process or transform the message in some way.

Consider this example:

    import json                                                                      
                                                                                 
    >>> s = '{"abc": 0, "cdf: 1}'                                                        
                                                                                 
    >>> try:                                                                             
    ...     d = json.loads(s)                                                            
    ... except Exception as e:                                                           
    ...     print(e)                                                                     
    ...     print(e.args)
    Unterminated string starting at: line 1 column 12 (char 11)
    ('Unterminated string starting at: line 1 column 12 (char 11)',)

Okay, I have caught an exception for which I have no control over how the
exception was raised. Now, imagine that I am writing an application that highlights
json errors in place. To do so, I would need the line and column numbers to
highlight the location of the error, and ideally I'd like to strip them from the base
message and just show that. You can see from my second print statement that
the line and column numbers were not passed as separate arguments. Thus
I need to parse the error message to extract them. Not a difficult job, but fragile.
Any change to the error message could break my code.

I don't know what this code smell is that people keep referring to, but to me,
that code would smell.

Jeff


> On 3 July 2017 at 09:59, Ken Kundert <python-ideas at shalmirane.com> wrote:
> > I think in trying to illustrate the existing behavior I made things more
> > confusing than they needed to be.  Let me try again.
> >
> > Consider this code.
> >
> >     >>> import Food
> >     >>> try:
> >     ...     import meals
> >     ... except NameError as e:
> >     ...     name = str(e).split("'")[1]   # <-- fragile code
> >     ...     from difflib import get_close_matches
> >     ...     candidates = ', '.join(get_close_matches(name, Food.foods, 1, 0.6))
> >     ...     print(f'{name}: not found. Did you mean {candidates}?')
> >
> > In this case *meals* instantiates a collection of foods. It is a Python file,
> > but it is also a data file (in this case the user knows Python, so Python is
> > a convenient data format). In that file thousands of foods may be instantiated.
> > If the user misspells a food, I would like to present the available
> > alternatives. To do so, I need the misspelled name.  The only way I can get it
> > is by parsing the error message.
> 
> As Steven pointed out, this is a pretty good example of a code smell.
> My feeling is that you may have just proved that Python isn't quite as
> good a fit for your data file format as you thought - or that your
> design has flaws. Suppose your user had a breakfast menu, and did
> something like:
> 
>     if now < lunchtim: # Should have been "lunchtime"
> 
> Your error handling will be fairly confusing in that case.
> 
> > That is the problem.  To write the error handler, I need the misspelled name.
> > The only way to get it is to extract it from the error message. The need to
> > unpack information that was just packed suggests that the packing was done too
> > early.  That is my point.
> 
> I don't have any problem with *having* the misspelled name as an
> attribute to the error, I just don't think it's going to be as useful
> as you hope, and it may indeed (as above) encourage people to use it
> without thinking about whether there might be problems with using
> error handling that way.
> 
> > Fundamentally, pulling the name out of an error message is a really bad coding
> > practice because it is fragile.  The code will likely break if the formatting or
> > the wording of the message changes.  But given the way the exception was
> > implemented, I am forced to choose between two unpleasant choices: pulling the
> > name from the error message or not giving the enhanced message at all.
> 
> Or using a different approach. ("Among our different approaches...!"
> :-)) Agreed that's also an unpleasant choice at this point.
> 
> > 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.  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.
> 
> I see it as a minor bug magnet, but not really a problem in principle.
> 
> > 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. It inadvertently puts obstacles in place that make it
> >    difficult to customize the message in the handler.
> 
> It's more about implicitly enforcing the policy of "catch errors over
> as small a section of code as practical". In your example, you're
> trapping NameError from anywhere in a "many thousands" of line file.
> That's about as far from the typical use of one or two lines in a try
> block as you can get.
> 
> > 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.
> 
> A small amount of value in a case we don't particularly want to encourage.
> Whether it's trivial comes down to implementation - I'll leave that to
> whoever writes the PR to demonstrate. (Although if it *is* trivial, is
> it something you could write a PR for?)
> 
> Also, given that this would be Python 3.7 only, would people needing
> this functionality (only you have expressed a need so far) be OK with
> either insisting their users go straight to Python 3.7, or including
> backward compatible code for older versions?
> 
> Overall, I'm -0 on this request (assuming it is trivial to implement -
> I certainly don't feel it's worth significant implementation effort).
> 
> Paul


More information about the Python-ideas mailing list