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
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