![](https://secure.gravatar.com/avatar/742dc8f125b4123c61f8e1b08c4f8a4d.jpg?s=120&d=mm&r=g)
All, Here is a proposal for enhancing the way BaseException handles arguments. -Ken Rationale ========= Currently, the base exception class takes all the unnamed arguments passed to an exception and saves them into args. In this way they are available to the exception handler. A commonly used and very useful idiom is to extract an error message from the exception by casting the exception to a string. If you do so while passing one argument, you get that argument as a string: >>> try: ... raise Exception('Hey there!') ... except Exception as e: ... print(str(e)) Hey there! However, if more than one argument is passed, you get the string representation of the tuple containing all the arguments: >>> try: ... raise Exception('Hey there!', 'Something went wrong.') ... except Exception as e: ... print(str(e)) ('Hey there!', 'Something went wrong.') That behavior does not seem very useful, and I believe it leads to people passing only one argument to their exceptions. An example of that is the system NameError exception: >>> try: ... foo ... except Exception as e: ... print('str:', str(e)) ... print('args:', e.args) str: name 'foo' is not defined args: ("name 'foo' is not defined",) Notice that the only argument is the error message. If you want access to the problematic name, you have to dig it out of the message. For example ... >>> 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, the missing name was needed but not directly available. Instead, the name must be extracted from the message, which is innately fragile. The same is true with AttributeError: the only argument is a message and the name of the attribute, if needed, must be extracted from the message. Oddly, with a KeyError it is the opposite situation, the name of the key is the argument and no message is included. With IndexError there is a message but no index. However none of these behaviors can be counted on; they could be changed at any time. When writing exception handlers it is often useful to have both a generic error message and access to the components of the message if for no other reason than to be able to construct a better error message. However, I believe that the way the arguments are converted to a string when there are multiple arguments discourages this. When reporting an exception, you must either give one argument or add a custom __str__ method to the exception. To do otherwise means that the exception handlers that catch your exception will not have a reasonable error message and so would be forced to construct one from the arguments. This is spelled out in PEP 352, which explicitly recommends that there be only one argument and that it be a helpful human readable message. Further it suggests that if more than one argument is required that Exception should be subclassed and the extra arguments should be attached as attributes. However, the extra effort required means that in many cases people just pass an error message alone. This approach is in effect discouraging people from adding additional arguments to exceptions, with the result being that if they are needed by the handler they have to be extracted from the message. It is important to remember that the person that writes the exception handler often does not raise the exception, and so they just must live with what is available. As such, a convention that encourages the person raising the exception to include all the individual components of the message should be preferred. That is the background. Here is my suggestion on how to improve this situation. Proposal ======== I propose that the Exception class be modified to allow passing a message template as a named argument that is nothing more than a format string that interpolates the exception arguments into an error message. If the template is not given, the arguments would simply be converted to strings individually and combined as in the print function. So, I am suggesting the BaseException class look something like this: class BaseException: def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs def __str__(self): template = self.kwargs.get('template') if template is None: sep = self.kwargs.get('sep', ' ') return sep.join(str(a) for a in self.args) else: return template.format(*self.args, **self.kwargs) Benefits ======== Now, NameError could be defined as: class NameError(Exception): pass A NameError would be raised with: try: raise NameError(name, template="name '{0}' is not defined.") except NameError as e: name = e.args[0] msg = str(e) ... Or, perhaps like this: try: raise NameError(name=name, template="name '{name}' is not defined.") except NameError as e: name = e.kwargs['name'] msg = str(e) ... One of the nice benefits of this approach is that the message printed can be easily changed after the exception is caught. For example, it could be converted to German. try: raise NameError(name, template="name '{0}' is not defined.") except NameError as e: print('{}: nicht gefunden.'.format(e.args[0])) A method could be provided to generate the error message from a custom format string: try: raise NameError(name, template="name '{0}' is not defined.") except NameError as e: print(e.render('{}: nicht gefunden.')) Another nice benefit of this approach is that both named and unnamed arguments to the exception are retained and can be processed by the exception handler. Currently this is only true of unnamed arguments. And since named arguments are not currently allowed, this proposal is completely backward compatible. Of course, with this change, the built-in exceptions should be changed to use this new approach. Hopefully over time, others will change the way they write exceptions to follow suit, making it easier to write more capable exception handlers. Conclusion ========== Sorry for the length of the proposal, but I thought it was important to give a clear rationale for it. Hopefully me breaking it into sections made it easier to scan. Comments?
![](https://secure.gravatar.com/avatar/5615a372d9866f203a22b2c437527bbb.jpg?s=120&d=mm&r=g)
On Sun, Jul 02, 2017 at 12:19:54PM -0700, Ken Kundert wrote: [...]
>>> try: ... foo ... except Exception as e: ... print('str:', str(e)) ... print('args:', e.args) str: name 'foo' is not defined args: ("name 'foo' is not defined",)
Notice that the only argument is the error message. If you want access to the problematic name, you have to dig it out of the message.
In the common case, you don't. You know the name because you know what name you just tried to look up: try: spam except NameError: name = "spam" # what else could it be? print("%s missing" % name) The general rule for try...except is that the try block should contain only the smallest amount of code that can fail, that you can deal with. So if it is important for your code to distinguish *which* name failed, you should put them in separate try blocks: # don't do this try: spam or eggs or cheese or aardvark except NameError as err: name = err.name # doesn't actually work if name == 'spam': spam = 'hello' elif name == 'eggs': eggs = 'hello' elif ... # you get the picture One problem with that is that it assumes that only *one* name might not exist. If the lookup of spam failed, that doesn't mean that eggs would have succeeded. Instead, we should write: # do this try: spam except NameError: spam = "hello" try: eggs except NameError: ... # and so on I won't categorically state that it is "never" useful to extract the name from NameError (or key from KeyError, index from IndexError, etc) but I'd consider that needing to do so programmatically may be a code smell: something which may be fine, but it smells a little fishy and requires a closer look. [...]
This is spelled out in PEP 352, which explicitly recommends that there be only one argument and that it be a helpful human readable message. Further it suggests that if more than one argument is required that Exception should be subclassed and the extra arguments should be attached as attributes.
No restriction is placed upon what may be passed in for args for backwards-compatibility reasons. In practice, though, only a single string argument should be used. This keeps the string representation of the exception to be a useful message about the exception that is human-readable; this is why the __str__ method special-cases on length-1 args value. Including programmatic information (e.g., an error code number) should be stored as a separate attribute in a subclass. https://www.python.org/dev/peps/pep-0352/
Proposal ========
I propose that the Exception class be modified to allow passing a message template as a named argument that is nothing more than a format string that interpolates the exception arguments into an error message. If the template is not given, the arguments would simply be converted to strings individually and combined as in the print function. So, I am suggesting the BaseException class look something like this:
[snip example implementation]
A NameError would be raised with:
try: raise NameError(name, template="name '{0}' is not defined.") except NameError as e: name = e.args[0] msg = str(e) ...
I think that's *exactly* what PEP 352 is trying to get away from: people having to memorize the order of arguments to the exception, so they know what index to give to extract them. And I tend to agree. I think that's a poor API. Assuming I ever find a reason to extract the name, I don't want to have to write `error.args[0]` to do so, not if I can write `error.name` instead. Look at ImportError and OSError: they define individual attributes for important error information: py> dir(OSError) [ # uninterest dunders trimmed ... 'args', 'characters_written', 'errno', 'filename', 'filename2', 'strerror', 'with_traceback']
Or, perhaps like this:
try: raise NameError(name=name, template="name '{name}' is not defined.") except NameError as e: name = e.kwargs['name'] msg = str(e) ...
That's not much of an improvement. Unless you can give a good rationale for why PEP 353 is wrong to recommend named attributes instead of error.args[index], I think this proposal is the wrong approach. -- Steve
![](https://secure.gravatar.com/avatar/742dc8f125b4123c61f8e1b08c4f8a4d.jpg?s=120&d=mm&r=g)
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. 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. 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. 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. The current approach taken by exceptions in Python makes that unnecessarily difficult. PEP 352 suggests that this situation can be handled with a custom exception, and that is certainly true, but that only works if the person writing the code that raises the exception anticipates the need for passing the components of the error message as separate arguments. But as we can see from the NameError, AttributeError, etc, they don't always do. And PEP 352 actively discourages them from doing so. 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. 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. 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. -Ken
![](https://secure.gravatar.com/avatar/d995b462a98fea412efa79d17ba3787a.jpg?s=120&d=mm&r=g)
On 3 July 2017 at 09:59, Ken Kundert <python-ideas@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
![](https://secure.gravatar.com/avatar/51dcf0a6e20c2734423118e7eee9e45d.jpg?s=120&d=mm&r=g)
On Mon, Jul 3, 2017 at 4:59 AM, Ken Kundert <python-ideas@shalmirane.com> wrote:
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.
1. You can pass an object with all the required information and an appropriate __str__() method to the exception constructor. 2. If you own the exception hierarchy, you can modify the __str__() method of the exception class. -- Juancarlo *Añez*
![](https://secure.gravatar.com/avatar/5615a372d9866f203a22b2c437527bbb.jpg?s=120&d=mm&r=g)
On Mon, Jul 03, 2017 at 06:29:05AM -0400, Juancarlo Añez wrote:
On Mon, Jul 3, 2017 at 4:59 AM, Ken Kundert <python-ideas@shalmirane.com> wrote:
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.
1. You can pass an object with all the required information and an appropriate __str__() method to the exception constructor.
Playing Devil's Advocate, or in this case, Ken's Advocate, I don't think that's a useful approach. Think of it from the perspective of the caller, who catches the exception. They have no way of forcing the callee (the code being called) to use that custom object with the appropriate __str__ method, so they can't rely on it: try: something() except NameError as err: msg = err.args[0] if hasattr(msg, 'name'): name = msg.name else: # extract using a regex or similar... name = ... which doesn't seem very friendly. And who says that this custom object with a custom __str__ actually uses 'name' as part of its API? I might be extracting a completely different attribute unrelated to the failed name lookup. I think Ken is right that *if* this problem is worth solving, we should solve it in BaseException (or at least StandardException), and not leave it up to individual users to write their own mutually incompatible APIs for extracting the name from NameError. -- Steve
![](https://secure.gravatar.com/avatar/5615a372d9866f203a22b2c437527bbb.jpg?s=120&d=mm&r=g)
On Wed, Jul 05, 2017 at 12:10:26AM +1000, Steven D'Aprano wrote:
I think Ken is right that *if* this problem is worth solving, we should solve it in BaseException (or at least StandardException),
Oops, I forgot that StandardException is gone. And it used to be spelled StandardError. -- Steve
![](https://secure.gravatar.com/avatar/5615a372d9866f203a22b2c437527bbb.jpg?s=120&d=mm&r=g)
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
![](https://secure.gravatar.com/avatar/92136170d43d61a5eeb6ea8784294aa2.jpg?s=120&d=mm&r=g)
I don't see the usefulness rich exception data as at all as limited as this. Here's some toy code that shows a use: ---- # For some reason, imports might not be ready immediately # Maybe flaky network drive, maybe need to install modules, etc # The overall program can do things too make them available lazy_import("foo", "bar", "baz", "blat") while True: try: x = foo(1) * bar(2) + baz(3)**blat(4) break except NameError as err: lazy_import(err.name) sleep(1) ---- I'd like the expression defining x all in one place together. Then I'd like to try again to import the functions used until they are available. In this case, I have assumed the "making available" might take some time, so I sleep in the loop. Of course I could write this in other ways also. But this one feels natural and concise. Of course, I probably *could* monkey-patch NameError with no language change. But not needing to would be a nice pattern. On Jul 4, 2017 7:04 AM, "Steven D'Aprano" <steve@pearwood.info> wrote: 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.
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
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. [...] 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.
In the least it can present the message to the user is the best possible way. Perhaps that means enforcing a
I don't think that's generally true. 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 _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/d6b9415353e04ffa6de5a8f3aaea0553.jpg?s=120&d=mm&r=g)
On 7/4/2017 3:32 PM, David Mertz wrote:
I don't see the usefulness rich exception data as at all as limited as this. Here's some toy code that shows a use:
----
# For some reason, imports might not be ready immediately # Maybe flaky network drive, maybe need to install modules, etc # The overall program can do things too make them available lazy_import("foo", "bar", "baz", "blat")
while True: try: x = foo(1) * bar(2) + baz(3)**blat(4) break except NameError as err: lazy_import(err.name <http://err.name>) sleep(1)
Alternate proposal: give the NameError class a .name instance method that extracts the name from the message. This should not increase the time to create an instance. You would then write 'err.name()' instead of 'err.name'. For 3.6 def name(self): msg = self.args[0] return msg[6:msg.rindex("'")] # current test try: xyz except NameError as e: print(name(e) == 'xyz') # Exceptions unittest to ensure that the method # stays synchronized with future versions of instances def test_nameerror_name(self): try: xyz except NameError as e: self.assertEqual(e.name(), 'xyz') Generalize to other exceptions. Further only-partially baked idea: Since exceptions are (in cpython) coded in C, I wonder if C data could be inexpensively attached to the instance to be retrieved and converted to python objects by methods when needed. -- Terry Jan Reedy
![](https://secure.gravatar.com/avatar/92136170d43d61a5eeb6ea8784294aa2.jpg?s=120&d=mm&r=g)
If a method, why not a property? On Jul 4, 2017 2:41 PM, "Terry Reedy" <tjreedy@udel.edu> wrote:
On 7/4/2017 3:32 PM, David Mertz wrote:
I don't see the usefulness rich exception data as at all as limited as this. Here's some toy code that shows a use:
----
# For some reason, imports might not be ready immediately # Maybe flaky network drive, maybe need to install modules, etc # The overall program can do things too make them available lazy_import("foo", "bar", "baz", "blat")
while True: try: x = foo(1) * bar(2) + baz(3)**blat(4) break except NameError as err: lazy_import(err.name <http://err.name>) sleep(1)
Alternate proposal: give the NameError class a .name instance method that extracts the name from the message. This should not increase the time to create an instance. You would then write 'err.name()' instead of ' err.name'. For 3.6
def name(self): msg = self.args[0] return msg[6:msg.rindex("'")]
# current test
try: xyz except NameError as e: print(name(e) == 'xyz')
# Exceptions unittest to ensure that the method # stays synchronized with future versions of instances
def test_nameerror_name(self): try: xyz except NameError as e: self.assertEqual(e.name(), 'xyz')
Generalize to other exceptions.
Further only-partially baked idea: Since exceptions are (in cpython) coded in C, I wonder if C data could be inexpensively attached to the instance to be retrieved and converted to python objects by methods when needed.
-- Terry Jan Reedy
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/d6b9415353e04ffa6de5a8f3aaea0553.jpg?s=120&d=mm&r=g)
On 7/4/2017 5:48 PM, David Mertz wrote:
If a method, why not a property?
Since the time to respond in human terms is trivial, I can imagine that this might be accepted. I just did not think of that option.
On Jul 4, 2017 2:41 PM, "Terry Reedy" <tjreedy@udel.edu <mailto:tjreedy@udel.edu>> wrote:
On 7/4/2017 3:32 PM, David Mertz wrote:
I don't see the usefulness rich exception data as at all as limited as this. Here's some toy code that shows a use:
----
# For some reason, imports might not be ready immediately # Maybe flaky network drive, maybe need to install modules, etc # The overall program can do things too make them available lazy_import("foo", "bar", "baz", "blat")
while True: try: x = foo(1) * bar(2) + baz(3)**blat(4) break except NameError as err: lazy_import(err.name <http://err.name> <http://err.name>) sleep(1)
Alternate proposal: give the NameError class a .name instance method that extracts the name from the message. This should not increase the time to create an instance. You would then write 'err.name <http://err.name>()' instead of 'err.name <http://err.name>'. For 3.6
def name(self): msg = self.args[0] return msg[6:msg.rindex("'")]
# current test
try: xyz except NameError as e: print(name(e) == 'xyz')
# Exceptions unittest to ensure that the method # stays synchronized with future versions of instances
def test_nameerror_name(self): try: xyz except NameError as e: self.assertEqual(e.name <http://e.name>(), 'xyz')
Generalize to other exceptions.
Further only-partially baked idea: Since exceptions are (in cpython) coded in C, I wonder if C data could be inexpensively attached to the instance to be retrieved and converted to python objects by methods when needed.
-- Terry Jan Reedy
_______________________________________________ Python-ideas mailing list Python-ideas@python.org <mailto:Python-ideas@python.org> https://mail.python.org/mailman/listinfo/python-ideas <https://mail.python.org/mailman/listinfo/python-ideas> Code of Conduct: http://python.org/psf/codeofconduct/ <http://python.org/psf/codeofconduct/>
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- Terry Jan Reedy
![](https://secure.gravatar.com/avatar/e88b046bd99c34714b60a04eaf51d334.jpg?s=120&d=mm&r=g)
Terry Reedy wrote:
Alternate proposal: give the NameError class a .name instance method that extracts the name from the message. This should not increase the time to create an instance. You would then write 'err.name()' instead of 'err.name'. For 3.6
def name(self): msg = self.args[0] return msg[6:msg.rindex("'")]
# current test
try: xyz except NameError as e: print(name(e) == 'xyz')
# Exceptions unittest to ensure that the method # stays synchronized with future versions of instances
def test_nameerror_name(self): try: xyz except NameError as e: self.assertEqual(e.name(), 'xyz')
Generalize to other exceptions.
This sounds a bit like "Nine out of ten guests in our restaurant want scrambled eggs -- let's scramble all eggs then as we can always unscramble on demand..." Regardless of usage frequency it feels wrong to regenerate the raw data from the string representation, even when it's possible. On the other hand, if AttributeError were implemented as class AttributeError: def __init__(self, obj, name): self.obj = obj self.name = name @property def message(self): ... # generate message constructing the error message could often be avoided. It would also encourage DRY and put an end to subtle variations in the error message like
NAME = "A" * 50 + "NobodyExpectsTheSpanishInquisition" exec("class {}: __slots__ = ()".format(NAME)) A = globals()[NAME] A().foo Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' object has no attribute 'foo' A().foo = 42 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANobodyExpectsTheSpanishInquisition' object has no attribute 'foo'
![](https://secure.gravatar.com/avatar/d6b9415353e04ffa6de5a8f3aaea0553.jpg?s=120&d=mm&r=g)
On 7/4/2017 10:03 AM, Steven D'Aprano wrote:
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.
There have been many proposals for what we might call RichExceptions, with more easily access information. But as Raymond Hettinger keeps pointing out, Python does not use exceptions only for (hopefully rare) errors. It also uses them as signals for flow control, both as an alternative form for alternation and for iteration. Alternation with try:except instead of if:else is common. In the try: unicode example above, the NameError is not an error. Until 2.2, IndexError served the role of StopIteration today, and can still be used for iteration. For flow control, richer exceptions just slow code execution.
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.
In other words, the richness of the exception should depend on the balance between the exception class's use as flow signal versus error reporting. Note that the exception reported usually does not know what the exception use will be, so raising a bare signal exception versus a rich report exception is not an option. An additional consideration, as Raymond has also pointed out, is the fractional overhead, which depends on the context of the exception raising. IndexError: list index out of range This is probably the most common flow signal after StopIteration. Also, as Raymond has noted, IndexError may be come from loops with short execution per loop. Attaching a *constant* string is very fast, to the consternation of people who would like the index reported. I believe there should usually be the workaround of naming a calculated index and accessing it in the exception clause. NameError: name 'xyz' is not defined This is less commonly a flow signal. (But not never!) Interpolating the name is worth the cost. OSError: whatever I believe this is even less commonly used as a flow signal. In any case, the context is usually a relatively slow operation, like opening a file (and reading it if successful).
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.
I hope not. One of the frustrations of trying to answer StackOverflow question is when the user used an environment that suppresses stacktraces and mangles exception names and messages. -- Terry Jan Reedy
![](https://secure.gravatar.com/avatar/95fb3c56e2e5322b0f9737fbb1eb9bce.jpg?s=120&d=mm&r=g)
On 2017-07-04 13:54, Terry Reedy wrote:
There have been many proposals for what we might call RichExceptions, with more easily access information. But as Raymond Hettinger keeps pointing out, Python does not use exceptions only for (hopefully rare) errors. It also uses them as signals for flow control, both as an alternative form for alternation and for iteration. Alternation with try:except instead of if:else is common. In the try: unicode example above, the NameError is not an error. Until 2.2, IndexError served the role of StopIteration today, and can still be used for iteration. For flow control, richer exceptions just slow code execution.
How significant is this slowdown in practical terms? Rejecting all "rich" exceptions just because they might add a bit of a slowdown seems premature to me. The information available from the rich exceptions has value that may or may not outweigh the performance hit. -- Brendan Barnwell "Do not follow where the path may lead. Go, instead, where there is no path, and leave a trail." --author unknown
![](https://secure.gravatar.com/avatar/d6b9415353e04ffa6de5a8f3aaea0553.jpg?s=120&d=mm&r=g)
On 7/4/2017 5:47 PM, Brendan Barnwell wrote:
On 2017-07-04 13:54, Terry Reedy wrote:
There have been many proposals for what we might call RichExceptions, with more easily access information. But as Raymond Hettinger keeps pointing out, Python does not use exceptions only for (hopefully rare) errors. It also uses them as signals for flow control, both as an alternative form for alternation and for iteration. Alternation with try:except instead of if:else is common. In the try: unicode example above, the NameError is not an error. Until 2.2, IndexError served the role of StopIteration today, and can still be used for iteration. For flow control, richer exceptions just slow code execution.
How significant is this slowdown in practical terms? Rejecting all "rich" exceptions just because they might add a bit of a slowdown seems premature to me. The information available from the rich exceptions has value that may or may not outweigh the performance hit.
I don't know if anyone has ever gone so far as to write a patch to test. I personally been on the side of wanting richer exceptions. So what has been the resistance? Speed is definitely one. Maybe space? Probably maintenance cost. Lack of interest among true 'core' (C competent) developers? My suggestion for speed is that we create exception instances as fast as possible with the information needed to produce python values and string representations on demand. Consumer pays. This is, after all, what we often do for other builtin classes. Consider ints. They have a bit length, which is an integer itself. But ints do not have a Python int data attribute for that. (If they did, the bit_length int would need its own bit_length attribute, and so on.) Instead, ints have a bit_length *method* to create a python int from internal data. Ints also have a decimal string representation, but we do not, as far as I know, precompute it. Ditto for the byte representation. -- Terry Jan Reedy
![](https://secure.gravatar.com/avatar/5615a372d9866f203a22b2c437527bbb.jpg?s=120&d=mm&r=g)
On Tue, Jul 04, 2017 at 11:37:51PM -0400, Terry Reedy wrote:
I personally been on the side of wanting richer exceptions.
Could you explain what you would use them for? Ken has give two use-cases which I personally consider are relatively niche, and perhaps even counter-productive: - translation into the user's native language; - providing some sort of "did you mean...?" functionality. Jeff Walker also suggested being able to extract the line and column from certain kinds of JSON errors. (But that would depend on the json module having an API that supports that use-case. You can't just say line_no = exception.args[0] if there's no guarantee that it actually will be the line number.) What would you use these for? I imagine you're thinking of this as the maintainer of IDLE?
So what has been the resistance? Speed is definitely one. Maybe space? Probably maintenance cost. Lack of interest among true 'core' (C competent) developers?
Where there is a clear and obvious need the core devs have spent the time to give the exception class a rich API for extracting useful information. See OSError, which offers named attributes for the errno, error message, Windows error number (when appropriate) and two file names. I expect that the fact that few of the other builtin or stdlib exceptions similarly offer named attributes is because nobody thought of it, or saw any need. -- Steve
![](https://secure.gravatar.com/avatar/4d4ea6148fef59dff9fa0fc8c309496a.jpg?s=120&d=mm&r=g)
Hi all, I want to point out that if it's not common to dispatch on values of exceptions it might be **because** it is hard to do or to know wether an exception will be structured or not. If Exceptions were by default more structured, if CPython would provide a default "StructuredException", or were the norm because CPython would use more of them – then you might see more use case, and new code patterns. In particular the fact that you have to catch and exception, then check values is annoying, I could see: try: ... except OSError(EACCES): # handle not permitted only except OSError(ENOENT): # handle does not exists only #implicit otherwise reraise Be a much cleaner way of filtering exception _if_ python were to provide that. Yes in Python 3 these are sometime their own subclass (FileNotFoundError), but isn't the fact that you _have_ to subclass be a hint that something might be wrong there ? You can see it in the fact that FilenotFoundError need to be caught _before_ OSError, it is not obvious to the beginner that FileNotFoundError is a subclass of OSError and that you can't refactor by changing the excepts order. Of course the custom subclass names are way more readable, (at least to me) but it is easy as an experienced Python programmer to forget what is difficult for newcommers. And this is (IMHO) one point which is not easy. One example I'm often wishing to have better filter is warnings where you have to use a regular expression to mute (or turn into errors) a subset of warnings. Of course most of these use case can be dealt with by Subclassing and having custom attributes (if you are in control of the raising code). But nobody will use it if it's not encouraged by the core.
Where there is a clear and obvious need the core devs have spent the time to give the exception class a rich API for extracting useful information. See OSError, which offers named attributes for the errno, error message, Windows error number (when appropriate) and two file names.
This does not encourage and show that doing structured exception is hard and _not_ the standard.
There should be one-- and preferably only one --obvious way to do it.
So structured or not ? Please find post signature an extremely non scientific grep of all the usage of structured information on Exceptions on my machine Thanks, -- Matthias $ rg -tpy ' e\.[^g]' | grep -v test | grep -v ENOENT | grep if | grep -v sympy | grep -v 'for e ' | grep -v getorg | grep -v ename |grep -v errno gh-activity/ghactivity.py: source = get_source_login(e.repo) if e.repo[0] == login else e.repo[0] pypi/store.py: if e.code == 204: pypi-legacy/store.py: if e.code == 204: cpython/Lib/asyncore.py: if e.args[0] not in _DISCONNECTED: cpython/Lib/nntplib.py: if e.response.startswith('480'): cpython/Lib/pdb.py: if e.startswith(text)] cpython/Lib/runpy.py: if e.name is None or (e.name != pkg_name and flit/flit/upload.py: if (not repo['is_warehouse']) and e.response.status_code == 403: gitsome/xonsh/execer.py: if (e.loc is None) or (last_error_line == e.loc.lineno and git-cpython/Lib/asyncore.py: if e.args[0] not in _DISCONNECTED: git-cpython/Lib/nntplib.py: if e.response.startswith('480'): git-cpython/Lib/pdb.py: if e.startswith(text)] git-cpython/Lib/runpy.py: if e.name is None or (e.name != pkg_name and jupyter_client/jupyter_client/manager.py: if e.winerror != 5: jupyterhub/jupyterhub/utils.py: if e.code >= 500: jupyterhub/jupyterhub/utils.py: if e.code != 599: procbuild/procbuild/builder.py: if not 'Resource temporarily unavailable' in e.strerror: qtconsole/qtconsole/console_widget.py: if e.mimeData().hasUrls(): qtconsole/qtconsole/console_widget.py: elif e.mimeData().hasText(): qtconsole/qtconsole/console_widget.py: if e.mimeData().hasUrls(): qtconsole/qtconsole/console_widget.py: elif e.mimeData().hasText(): qtconsole/qtconsole/console_widget.py: if e.mimeData().hasUrls(): qtconsole/qtconsole/console_widget.py: elif e.mimeData().hasText(): xonsh/xonsh/execer.py: if (e.loc is None) or (last_error_line == e.loc.lineno and xonsh/xonsh/execer.py: if not greedy and maxcol in (e.loc.column + 1, e.loc.column): xonsh/xonsh/proc.py: r = e.code if isinstance(e.code, int) else int(bool(e.code)) django/django/forms/fields.py: if hasattr(e, 'code') and e.code in self.error_messages: django/django/forms/fields.py: errors.extend(m for m in e.error_list if m not in errors) django/django/http/multipartparser.py: if not e.connection_reset: cpython/Lib/asyncio/streams.py: if self._buffer.startswith(sep, e.consumed): cpython/Lib/idlelib/MultiCall.py: if not APPLICATION_GONE in e.args[0]: cpython/Lib/idlelib/MultiCall.py: if not APPLICATION_GONE in e.args[0]: cpython/Lib/idlelib/MultiCall.py: if not APPLICATION_GONE in e.args[0]: cpython/Lib/multiprocessing/connection.py: if e.winerror == _winapi.ERROR_BROKEN_PIPE: cpython/Lib/multiprocessing/connection.py: if e.winerror != _winapi.ERROR_NO_DATA: cpython/Lib/multiprocessing/connection.py: if e.winerror not in (_winapi.ERROR_SEM_TIMEOUT, cpython/Lib/multiprocessing/process.py: if not e.args: git-cpython/cpython/Lib/asyncore.py: if e.args[0] not in (EBADF, ECONNRESET, ENOTCONN, ESHUTDOWN, git-cpython/cpython/Lib/nntplib.py: if user and e.response[:3] == '480': git-cpython/cpython/Lib/socket.py: if e.args[0] == EINTR: git-cpython/cpython/Lib/socket.py: if e.args[0] == EINTR: git-cpython/cpython/Lib/socket.py: if e.args[0] == EINTR: git-cpython/cpython/Lib/socket.py: if e.args[0] == EINTR: git-cpython/cpython/Lib/socket.py: if e.args[0] == EINTR: git-cpython/Lib/asyncio/streams.py: if self._buffer.startswith(sep, e.consumed): ipyparallel/ipyparallel/client/client.py: if e.engine_info: ipython/IPython/core/inputtransformer.py: if 'multi-line string' in e.args[0]: ipython/IPython/core/inputsplitter.py: if 'multi-line string' in e.args[0]: ipython/IPython/core/inputsplitter.py: elif 'multi-line statement' in e.args[0]: git-cpython/Lib/idlelib/multicall.py: if not APPLICATION_GONE in e.args[0]: git-cpython/Lib/idlelib/multicall.py: if not APPLICATION_GONE in e.args[0]: git-cpython/Lib/idlelib/multicall.py: if not APPLICATION_GONE in e.args[0]: git-cpython/Lib/multiprocessing/connection.py: if e.winerror == _winapi.ERROR_BROKEN_PIPE: git-cpython/Lib/multiprocessing/connection.py: if e.winerror != _winapi.ERROR_NO_DATA: git-cpython/Lib/multiprocessing/connection.py: if e.winerror not in (_winapi.ERROR_SEM_TIMEOUT, git-cpython/Lib/multiprocessing/process.py: if not e.args: jedi/jedi/api/completion.py: if e.error_leaf.value == '.': meeseeksbox/meeseeksdev/meeseeksbox/commands.py: if ('git commit --allow-empty' in e.stderr) or ('git commit --allow-empty' in e.stdout): meeseeksbox/meeseeksdev/meeseeksbox/commands.py: elif "after resolving the conflicts" in e.stderr: notebook/notebook/notebook/handlers.py: if e.status_code == 404 and 'files' in path.split('/'): pandas/pandas/core/strings.py: if len(e.args) >= 1 and re.search(p_err, e.args[0]): pip/pip/_vendor/pyparsing.py: if not e.mayReturnEmpty: prompt_toolkit/prompt_toolkit/terminal/vt100_output.py: elif e.args and e.args[0] == 0: rust/src/etc/dec2flt_table.py:range of exponents e. The output is one array of 64 bit significands and rust/src/etc/htmldocck.py: if e.tail: rust/src/etc/htmldocck.py: if attr in e.attrib: setuptools/pkg_resources/_vendor/pyparsing.py: if not e.mayReturnEmpty: scikit-learn/sklearn/datasets/mldata.py: if e.code == 404: numpy/tools/npy_tempita/__init__.py: if e.args: django/django/core/files/images.py: if e.args[0].startswith("Error -5"): django/django/core/management/base.py: if e.is_serious() cpython/Lib/xml/etree/ElementInclude.py: if e.tag == XINCLUDE_INCLUDE: cpython/Lib/xml/etree/ElementInclude.py: if e.tail: cpython/Lib/xml/etree/ElementInclude.py: elif e.tag == XINCLUDE_FALLBACK: cpython/Lib/xml/etree/ElementPath.py: if e.tag == tag: git-cpython/cpython/Demo/pdist/rcvs.py: if not e.commitcheck(): git-cpython/cpython/Demo/pdist/rcvs.py: if e.commit(message): git-cpython/cpython/Demo/pdist/rcvs.py: e.diff(opts) git-cpython/cpython/Demo/pdist/rcvs.py: if e.proxy is None: git-cpython/cpython/Lib/multiprocessing/connection.py: if e.args[0] != win32.ERROR_PIPE_CONNECTED: git-cpython/cpython/Lib/multiprocessing/connection.py: if e.args[0] != win32.ERROR_PIPE_CONNECTED: git-cpython/cpython/Lib/multiprocessing/connection.py: if e.args[0] not in (win32.ERROR_SEM_TIMEOUT, git-cpython/cpython/Lib/multiprocessing/process.py: if not e.args: git-cpython/Lib/xml/etree/ElementInclude.py: if e.tag == XINCLUDE_INCLUDE: git-cpython/Lib/xml/etree/ElementInclude.py: if e.tail: git-cpython/Lib/xml/etree/ElementInclude.py: elif e.tag == XINCLUDE_FALLBACK: git-cpython/Lib/xml/etree/ElementPath.py: if e.tag == tag: pip/pip/_vendor/distlib/locators.py: if e.code != 404: pip/pip/_vendor/distlib/scripts.py: if e.startswith('.py'): pip/pip/_vendor/html5lib/serializer.py: if not e.endswith(";"): pythondotorg/blogs/management/commands/update_blogs.py: if e.pub_date < entry['pub_date']: scikit-learn/doc/tutorial/machine_learning_map/svg2imagemap.py: if e.nodeName == 'g': scikit-learn/doc/tutorial/machine_learning_map/svg2imagemap.py: if e.hasAttribute('transform'): scikit-learn/doc/tutorial/machine_learning_map/pyparsing.py: if not e.mayReturnEmpty: scikit-learn/doc/tutorial/machine_learning_map/pyparsing.py: if not e.mayReturnEmpty: scikit-learn/doc/tutorial/machine_learning_map/pyparsing.py: if e.mayReturnEmpty: scikit-learn/doc/tutorial/machine_learning_map/pyparsing.py: if e.mayReturnEmpty: scikit-learn/doc/tutorial/machine_learning_map/pyparsing.py: if not e.mayReturnEmpty: django/django/db/backends/mysql/base.py: if e.args[0] in self.codes_for_integrityerror: django/django/db/backends/mysql/base.py: if e.args[0] in self.codes_for_integrityerror: django/django/db/models/fields/__init__.py: if hasattr(e, 'code') and e.code in self.error_messages: git-cpython/cpython/Lib/xml/etree/ElementInclude.py: if e.tag == XINCLUDE_INCLUDE: git-cpython/cpython/Lib/xml/etree/ElementInclude.py: if e.tail: git-cpython/cpython/Lib/xml/etree/ElementInclude.py: elif e.tag == XINCLUDE_FALLBACK: pip/pip/_vendor/requests/packages/urllib3/contrib/pyopenssl.py: if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'): pip/pip/_vendor/requests/packages/urllib3/contrib/pyopenssl.py: if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'): pip/pip/_vendor/requests/packages/urllib3/contrib/socks.py: if e.socket_err: On Wed, Jul 5, 2017 at 1:36 PM, Steven D'Aprano <steve@pearwood.info> wrote:
On Tue, Jul 04, 2017 at 11:37:51PM -0400, Terry Reedy wrote:
I personally been on the side of wanting richer exceptions.
Could you explain what you would use them for? Ken has give two use-cases which I personally consider are relatively niche, and perhaps even counter-productive:
- translation into the user's native language;
- providing some sort of "did you mean...?" functionality.
Jeff Walker also suggested being able to extract the line and column from certain kinds of JSON errors. (But that would depend on the json module having an API that supports that use-case. You can't just say line_no = exception.args[0] if there's no guarantee that it actually will be the line number.)
What would you use these for? I imagine you're thinking of this as the maintainer of IDLE?
So what has been the resistance? Speed is definitely one. Maybe space? Probably maintenance cost. Lack of interest among true 'core' (C competent) developers?
Where there is a clear and obvious need the core devs have spent the time to give the exception class a rich API for extracting useful information. See OSError, which offers named attributes for the errno, error message, Windows error number (when appropriate) and two file names.
I expect that the fact that few of the other builtin or stdlib exceptions similarly offer named attributes is because nobody thought of it, or saw any need.
-- Steve _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/5615a372d9866f203a22b2c437527bbb.jpg?s=120&d=mm&r=g)
On Wed, Jul 05, 2017 at 03:29:35PM +0200, Matthias Bussonnier wrote:
Hi all,
I want to point out that if it's not common to dispatch on values of exceptions it might be **because** it is hard to do or to know wether an exception will be structured or not.
It "might be" for a lot of reasons. The argument that "if Python did X, we might find a use for X" could be used to justify any idea at all, regardless of its usefulness. I found your post ironic -- after spending many paragraphs complaining that exceptions aren't structured enough, that people won't write structured exception classes or make use of structured exceptions "if it's not encouraged by the core" (your words), you then run a grep over your code and find (by my rough count) nearly 100 examples of structured exceptions. So it seems to me that third party libraries are already doing what you say they won't do unless the core exceptions lead the way: providing structured exceptions. Just a handful of examples from your grep:
gh-activity/ghactivity.py: e.repo pypi/store.py: e.code cpython/Lib/nntplib.py: e.response cpython/Lib/runpy.py: e.name jupyter_client/jupyter_client/manager.py: e.winerror jupyterhub/jupyterhub/utils.py: e.code qtconsole/qtconsole/console_widget.py: e.mimeData xonsh/xonsh/execer.py: e.loc django/django/forms/fields.py: e.error_list django/django/http/multipartparser.py: e.connection_reset cpython/Lib/asyncio/streams.py: e.consumed ipyparallel/ipyparallel/client/client.py: e.engine_info jedi/jedi/api/completion.py: e.error_leaf
and more. Third parties *are* providing rich exception APIs where it makes sense to do so, using the interface encouraged by PEP 352 (named attributes), without needing a default "StructuredException" in the core language.
If Exceptions were by default more structured, if CPython would provide a default "StructuredException",
As I said earlier, perhaps I'm just not sufficiently imaginative, but I don't think you can solve the problem of providing better structure to exceptions with a simple hack on BaseException. (And a complicated hack would have costs of its own.) I've come to the conclusion that there's no substitute for tackling each individual exception class individually, deciding whether or not it makes sense for it to be structured, and give it the structure needed for that exception.
or were the norm because CPython would use more of them – then you might see more use case, and new code patterns.
And you might not. You might spend hours or days or weeks adding bloat to exceptions for no purpose. That's why we typically insist on good use-cases for new features before accepting them. If we accepted even 10% of the ideas proposed, Python would be a bloated, inconsistent, hard to use mess. (Actually I plucked that number out of thin air. It would be an interesting project to look at what proportion of ideas end up being accepted.) -- Steve
![](https://secure.gravatar.com/avatar/cdde45d5fb0ef417b96c215152d028a7.jpg?s=120&d=mm&r=g)
Hi, On Wed, 5 Jul 2017 at 16:41 Steven D'Aprano <steve@pearwood.info> wrote:
and more. Third parties *are* providing rich exception APIs where it makes sense to do so, using the interface encouraged by PEP 352 (named attributes), without needing a default "StructuredException" in the core language.
Your arguments might be used to dismiss anything. If people are already doing $thing, clearly they don't need help from the language. If they're not already doing it, any language feature would be pointless. Ed
![](https://secure.gravatar.com/avatar/5615a372d9866f203a22b2c437527bbb.jpg?s=120&d=mm&r=g)
On Wed, Jul 05, 2017 at 04:12:29PM +0000, Ed Kellett wrote:
Hi,
On Wed, 5 Jul 2017 at 16:41 Steven D'Aprano <steve@pearwood.info> wrote:
and more. Third parties *are* providing rich exception APIs where it makes sense to do so, using the interface encouraged by PEP 352 (named attributes), without needing a default "StructuredException" in the core language.
Your arguments might be used to dismiss anything.
Do you have an answer for why the argument is wrong? People *are* writing structured exceptions, which undercuts the argument that we must do something because if we don't lead the way others won't. The argument that "we must do something, this is something, therefore we must do it" doesn't cut any ice here. Read the Zen of Python: Now is better than never. Although never is often better than *right* now. The burden is not on me to prove that this idea is a bad idea. I don't have to prove this is a bad idea. The burden is on those who want to make this change to demonstrate to the satisfaction of the core developers that their idea: - solves a genuine problem; - without introducing worse problems; - that it will do what they expect it to do; - and that the change is worth the effort in implementation and the cost to the language (bloat and churn). If proponents of the idea can't do that, then the status quo wins: http://www.curiousefficiency.org/posts/2011/02/status-quo-wins-stalemate.htm... I've had a number private emails complaining that I'm "too negative" for this list because I pointed out flaws. Do people think that we make Python better by introducing flawed changes that don't solve the problem they're supposed to solve? (I'm not going to name names, you know who you are.) If people want this change, it's not enough to get all snarky and complain that critics are "too negative" or "too critical of minor problems". You need to start by **addressing the criticism**. In the very first reply to Ken's initial proposal, it was pointed out that his plan goes against PEP 352 and that he would have to address why that PEP is wrong to encourage named attributes over positional arguments in exception.args. As far as I can see, nobody has even attempted to do that. I think that's the place to start: if your plan for giving exceptions structure is to just dump everything into an unstructured args list with no guaranteed order, then you're not actually giving exceptions structure and you're not solving the problem. (like the fact that the idea doesn't actually solve the problem it is intended to). You know what? I don't have to prove anything here. It's up to the people wanting this change to prove that it is useful, worth the effort, and that it will do what they expect. Ken suggested a concrete change to BaseException to solve a real lack. His solution can't work, for reasons I've already gone over, but at least he's made an attempt at a solution. (He hasn't demonstrated that there is a real problem If people are already
doing $thing, clearly they don't need help from the language. If they're not already doing it, any language feature would be pointless.
Ed
![](https://secure.gravatar.com/avatar/3d04c4229ca950ae9228531e3f8315b3.jpg?s=120&d=mm&r=g)
I am one of those that also find you to be too negative. I find your critiques to be useful. You often raise issues that had not occurred to me. But then you go further an make pronouncements which I think go too far. For example:
the idea doesn't actually solve the problem it is intended to
or
His solution can't work
or
He hasn't demonstrated that there is a real problem
None of these ring true to me. Rather it seems like you just don't like the approach he has taken. You are often dismissive of other peoples code, experiences and opinions. For example, in Ken's first post he used NameError as an example, though he indicated that the same issue occurred widely in the language. Rather than asking for further examples, you immediately dismissed his idea as 'code smell' largely on his use of NameError. That is a pretty derogatory response that really cannot be argued against because it is simply your opinion. I think we all understand that the chance of actually changing the language by sending an idea to python-ideas@python.org is pretty low, but it can be fun and educational to discuss the ideas. And doing so makes this more of a community. However it seems like you feel you must play the role of a barrier that protects the language from poorly conceived ideas. I don't think we need that. The good ideas are naturally sorted from the bad ones through prolonged discussion. And the early criticism can take the joy out of the process and can lead to the original poster becoming defensive (for example, consider how you just reacted to a little criticism). They can feel like people have not taken the time to understand their idea before criticizing it. It is worth noting that Ken felt the need to start fresh after your initial criticism. When people send an idea to this mailing list they are exposing themselves, and the criticism can really hurt. It is like they are being told that they are not good enough to contribute. We should be sensitive to that, and focus first on the good aspects of their idea. We should defer our criticisms and perhaps try couching them as suggested improvements. I find you a very valuable member of the community, but I have often wished that you would take a more positive approach. Jeff 05.07.2017, 11:33, "Steven D'Aprano" <steve@pearwood.info>:
On Wed, Jul 05, 2017 at 04:12:29PM +0000, Ed Kellett wrote:
Hi,
On Wed, 5 Jul 2017 at 16:41 Steven D'Aprano <steve@pearwood.info> wrote:
> and more. Third parties *are* providing rich exception APIs where it > makes sense to do so, using the interface encouraged by PEP 352 (named > attributes), without needing a default "StructuredException" in the > core language. >
Your arguments might be used to dismiss anything.
Do you have an answer for why the argument is wrong? People *are* writing structured exceptions, which undercuts the argument that we must do something because if we don't lead the way others won't.
The argument that "we must do something, this is something, therefore we must do it" doesn't cut any ice here. Read the Zen
And you seem rather dismissive of other peoples code and experiences. For example, in Ken's first post he used NameError as example, though he indicated that the same issue occurred widely in the language. Yet you dismissed his idea as 'code smell'. That is a pretty derogatory response that does not allow for the fact that not all code needs to be hardened to be useful. One of the nice things about Python over other languages such as Java and C++ is that you can throw something together quickly that works, and that may be what is needed. If you are throwing a script together for your own use, you often don't need the harden the program like you would if you are distributing it to the world. You've said repeatedly that using unnamed arguments would be 05.07.2017, 11:33, "Steven D'Aprano" <steve@pearwood.info>:
On Wed, Jul 05, 2017 at 04:12:29PM +0000, Ed Kellett wrote:
Hi,
On Wed, 5 Jul 2017 at 16:41 Steven D'Aprano <steve@pearwood.info> wrote:
and more. Third parties *are* providing rich exception APIs where it makes sense to do so, using the interface encouraged by PEP 352 (named attributes), without needing a default "StructuredException" in the core language.
Your arguments might be used to dismiss anything.
Do you have an answer for why the argument is wrong? People *are* writing structured exceptions, which undercuts the argument that we must do something because if we don't lead the way others won't.
Tof Python:
Now is better than never. Although never is often better than *right* now.
The burden is not on me to prove that this idea is a bad idea. I don't have to prove this is a bad idea. The burden is on those who want to make this change to demonstrate to the satisfaction of the core developers that their idea:
- solves a genuine problem;
- without introducing worse problems;
- that it will do what they expect it to do;
- and that the change is worth the effort in implementation and the cost to the language (bloat and churn).
If proponents of the idea can't do that, then the status quo wins:
http://www.curiousefficiency.org/posts/2011/02/status-quo-wins-stalemate.htm...
I've had a number private emails complaining that I'm "too negative" for this list because I pointed out flaws. Do people think that we make Python better by introducing flawed changes that don't solve the problem they're supposed to solve?
(I'm not going to name names, you know who you are.)
If people want this change, it's not enough to get all snarky and complain that critics are "too negative" or "too critical of minor problems". You need to start by **addressing the criticism**.
In the very first reply to Ken's initial proposal, it was pointed out that his plan goes against PEP 352 and that he would have to address why that PEP is wrong to encourage named attributes over positional arguments in exception.args. As far as I can see, nobody has even attempted to do that.
I think that's the place to start: if your plan for giving exceptions structure is to just dump everything into an unstructured args list with no guaranteed order, then you're not actually giving exceptions structure and you're not solving the problem.
(like the fact that the idea doesn't actually solve the problem it is intended to).
You know what? I don't have to prove anything here. It's up to the people wanting this change to prove that it is useful, worth the effort, and that it will do what they expect.
Ken suggested a concrete change to BaseException to solve a real lack. His solution can't work, for reasons I've already gone over, but at least he's made an attempt at a solution. (He hasn't demonstrated that there is a real problem
If people are already
doing $thing, clearly they don't need help from the language. If they're not already doing it, any language feature would be pointless.
Ed
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/d67ab5d94c2fed8ab6b727b62dc1b213.jpg?s=120&d=mm&r=g)
On Thu, Jul 6, 2017 at 10:46 AM, Jeff Walker <jeff.walker00@yandex.com> wrote:
I am one of those that also find you to be too negative. I find your critiques to be useful. You often raise issues that had not occurred to me. But then you go further an make pronouncements which I think go too far. For example:
the idea doesn't actually solve the problem it is intended to
or
His solution can't work
or
He hasn't demonstrated that there is a real problem
None of these ring true to me. Rather it seems like you just don't like the approach he has taken.
You are often dismissive of other peoples code, experiences and opinions. For example, in Ken's first post he used NameError as an example, though he indicated that the same issue occurred widely in the language. Rather than asking for further examples, you immediately dismissed his idea as 'code smell' largely on his use of NameError. That is a pretty derogatory response that really cannot be argued against because it is simply your opinion.
For what it's worth, I'm in favour of Steven's "too negative" approach - or rather, I don't think his style is too negative. Yes, it's a bit rough and uncomfortable to be on the receiving end of it, but it's exactly correct. All three of the statements you quote are either provably true from the emails in this thread, or are at least plausible. If you think he's wrong to say them, *say so*, and ask him to justify them. Perhaps what we need is a "falsehoods programmers believe about python-ideas" collection. I'll start it: * All ideas are worthy of respect. * My use-case is enough justification for adding something to the language. * Criticism is bad. Ideas should be welcomed just because they're ideas. * "Why not?" is enough reason to do something. * PyPI doesn't exist. * I don't need a concrete use-case; a rough idea of "this could be neat" is enough. * Performance doesn't matter. * Performance matters. * You must hate me, because you're picking holes in my brilliant idea. * Code smell is inherently bad. * Criticism means your idea is bad. * Criticism means your idea is good. * Criticism means your idea is interesting. * CPython is the only Python there is. As usual, many of these wouldn't be articulated, but you'll find that a lot of people's posts have been written with these as unwitting assumptions. I'll leave those thoughts with you. ChrisA
![](https://secure.gravatar.com/avatar/3d04c4229ca950ae9228531e3f8315b3.jpg?s=120&d=mm&r=g)
Stephen, These statements do not ring true to me. I have been following the conversation closely and I have not seen support for any of them. Perhaps I missed it. Could you please expand on these statements:
the idea doesn't actually solve the problem it is intended to
Specifically Ken started by saying that it should not be necessary to parse the messages to get the components of the message. He then gave an example where he was able to access the components of the message without parsing the message. So how is it that he is not solving the problem he intended to solve?
His solution can't work
Again, he gave an example where he was able to access the components of the message without parsing the message. Yet you claim his solution cannot work. Is his example wrong?
He hasn't demonstrated that there is a real problem
You yourself admitted that parsing a message to extract the components is undesirable. Ken and others, including myself, gave examples where this was necessary. Each example was given as either being a real problem or representative of a real problem. Are we all wrong? Jeff
![](https://secure.gravatar.com/avatar/d995b462a98fea412efa79d17ba3787a.jpg?s=120&d=mm&r=g)
On 6 July 2017 at 02:53, Jeff Walker <jeff.walker00@yandex.com> wrote:
Could you please expand on these statements:
the idea doesn't actually solve the problem it is intended to
Specifically Ken started by saying that it should not be necessary to parse the messages to get the components of the message. He then gave an example where he was able to access the components of the message without parsing the message. So how is it that he is not solving the problem he intended to solve?
Just to add my perspective here, his proposed solution (to modify BaseException) doesn't include any changes to the derived exceptions that would need to store the components. To use the (already over-used) NameError example, Ken's proposal doesn't include any change to how NameError exceptions are raised to store the name separately on the exception. So *as the proposal stands* it doesn't allow users to extract components of any exceptions, simply because the proposal doesn't suggest changing exceptions to *store* those components.
His solution can't work
Again, he gave an example where he was able to access the components of the message without parsing the message. Yet you claim his solution cannot work. Is his example wrong?
Yes. Because he tries to extract the name component of a NameError, and yet that component isn't stored anywhere - under his proposal or under current CPython.
He hasn't demonstrated that there is a real problem
You yourself admitted that parsing a message to extract the components is undesirable. Ken and others, including myself, gave examples where this was necessary. Each example was given as either being a real problem or representative of a real problem. Are we all wrong?
He's given examples of use cases. To that extent, Steven is being a touch absolute here. However, there has been some debate over whether those examples are valid. We've had multiple responses pointing out that the code examples aren't restricting what's in the try block sufficiently tightly, for example (the NameError case in particular was importing a file which, according to Ken himself, had potentially *thousands* of places where NameError could be raised). It's possible that introspecting exceptions is the right way to design a solution to this particular problem, but it does go against the normal design principles that have been discussed on this list and elsewhere many times. So, to demonstrate that there's a problem, it's necessary to address the question of whether the code could in fact have been written in a different manner that avoided the claimed problem. That's obviously not a black and white situation - making it easier to write code in a certain style is a valid reason for suggesting an enhancement - but the debate has edged towards a stance of "this is needed" (as in, the lack of it is an issue) rather than "this would be an improvement". That's not what Ken said, though, and we all bear a certain responsibility for becoming a little too entrenched in our positions. As far as whether Steven's (or anyone's) comments are too negative, I think the pushback is reasonable. In particular, as far as I know Ken is not a first-time contributor here, so he's almost certainly aware of the sorts of concerns that come up in discussions like this, and with that context I doubt he's offended by the reception his idea got (indeed, his responses on this thread have been perfectly sensible and positive). I do think we need to be more sensitive with newcomers, and Chris Angelico's idea of a "falsehoods programmers believe about python-ideas" collection may well be a good resource to gently remind newcomers of some of the parameters of discussion around here. You also say
but it can be fun and educational to discuss the ideas
Indeed, very much so. I've certainly learned a lot about language and API design from discussions here over the years. But again, that's the point - the biggest things I've learned are about how *hard* good design is, and how important it is to think beyond your own personal requirements. Most of the "negative" comments I've seen on this list have been along those lines - reminding people that there's a much wider impact for their proposals, and that benefits need to be a lot more compelling than you originally thought. That's daunting, and often discouraging (plenty of times, I've been frustrated by the fact that proposals that seem good to be are blocked by the risk that someone might have built their whole business around a corner case that I'd never thought of, or cared about). But it's the reality and it's an immensely valuable lesson to learn (IMO). Paul
![](https://secure.gravatar.com/avatar/3312219849e076b5e99adb78b9e60b58.jpg?s=120&d=mm&r=g)
On Thu, Jul 6, 2017 at 5:58 AM, Paul Moore <p.f.moore@gmail.com> wrote:
To use the (already
over-used) NameError example, Ken's proposal doesn't include any
change to how NameError exceptions are raised to store the name separately on the exception.
Maybe I'm misunderstanding you, but the proposal has a clear example of raising NameError and getting the name attribute from the exception instance: try: raise NameError(name=name, template="name '{name}' is not defined.") except NameError as e: name = e.kwargs['name'] msg = str(e) ... Yes. Because he tries to extract the name component of a NameError,
and yet that component isn't stored anywhere - under his proposal or
under current CPython.
I'm not sure what you mean by "extract", but the proposal calls for the name to be passed as a keyword argument (see above) and stored in self.kwargs: class BaseException: def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs I think this code does what you're asking it to do, right? (N.B. BaseException is written in C, so this Python code is presumably illustrative, not an actual implementation.)
![](https://secure.gravatar.com/avatar/d995b462a98fea412efa79d17ba3787a.jpg?s=120&d=mm&r=g)
On 6 July 2017 at 18:59, Mark E. Haase <mehaase@gmail.com> wrote:
On Thu, Jul 6, 2017 at 5:58 AM, Paul Moore <p.f.moore@gmail.com> wrote:
To use the (already
over-used) NameError example, Ken's proposal doesn't include any change to how NameError exceptions are raised to store the name separately on the exception.
Maybe I'm misunderstanding you, but the proposal has a clear example of raising NameError and getting the name attribute from the exception instance:
But no-one manually raises NameError, so Ken's example wouldn't work with "real" NameErrors. If Ken was intending to present a use case that did involve manually-raised NameError exceptions, then he needs to show the context to demonstrate why manually raising NameError rather than a custom exception (which can obviously work like he wants) is necessary. Paul
![](https://secure.gravatar.com/avatar/f3ba3ecffd20251d73749afbfa636786.jpg?s=120&d=mm&r=g)
On 7 July 2017 at 10:26, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Paul Moore wrote:
But no-one manually raises NameError, so Ken's example wouldn't work with "real" NameErrors.
Part of his suggestion was that core and stdlib code would move towards using attributes. The NameError example makes sense in that context.
I haven't been following the thread, so making sure this is stated explicitly: as a matter of general policy, we're OK with enhancing builtin exception types to store additional state for programmatic introspection (when we aren't, it's usually because the exception is often raised and then handled in contexts where it never actually gets instantiated, and you can't use that trick anymore if you add meaningful state to the exception instance). Actually doing that isn't especially hard conceptually, it's just tedious in practice, since somebody has to do the work to: 1. Decide what field they want to add 2. Figure out how to support that in the C API without breaking backwards compatibility 3. Go through the code base to actually set the new field in the relevant locations By contrast, we're incredibly wary of trying to enhance exception behaviour by way of `BaseException` changes due to our past negative experiences with that during the original implementation of PEP 352: https://www.python.org/dev/peps/pep-0352/#retracted-ideas In principle, such approaches sound great. In practice, they tend to fall apart once they try to cope with the way CPython's internals (as opposed to user level Python code) creates and manipulates exceptions. Cheers, Nick. P.S. It isn't a coincidence that the import system's exceptions started to become significantly more informative *after* Brett rewrote most of it in Python :) -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
![](https://secure.gravatar.com/avatar/f3ba3ecffd20251d73749afbfa636786.jpg?s=120&d=mm&r=g)
On 7 July 2017 at 12:18, Nick Coghlan <ncoghlan@gmail.com> wrote:
By contrast, we're incredibly wary of trying to enhance exception behaviour by way of `BaseException` changes due to our past negative experiences with that during the original implementation of PEP 352: https://www.python.org/dev/peps/pep-0352/#retracted-ideas
In principle, such approaches sound great. In practice, they tend to fall apart once they try to cope with the way CPython's internals (as opposed to user level Python code) creates and manipulates exceptions.
To elaborate on the potential problem with the specific proposal in this thread: adding arbitrary kwargs support to BaseException would be straightforward. Adding arbitrary kwargs support to the dozens of exceptions defined as builtins and in the standard library would *not* necessarily be straightforward, *and* would potentially get in the way of adding more clearly defined attributes to particular subclasses in the future. It would also create potential inconsistencies with third party exceptions which may or may not start accepting arbitrary keywords depending on how they're defined. As a result, our advice is to *avoid* trying to come up with systemic fixes for structured exception handling, and instead focus on specific use cases of "I want *this* exception type to have *that* attribute for *these* reasons". Those kinds of proposals usually don't even rise to the level of needing a PEP - they're just normal RFEs on the issue tracker. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
![](https://secure.gravatar.com/avatar/b8efb5dcb59ea1048c4763b7507d3343.jpg?s=120&d=mm&r=g)
On Fri, Jul 7, 2017 at 7:28 AM, Nick Coghlan <ncoghlan@gmail.com> wrote:
As a result, our advice is to *avoid* trying to come up with systemic fixes for structured exception handling, and instead focus on specific use cases of "I want *this* exception type to have *that* attribute for *these* reasons". Those kinds of proposals usually don't even rise to the level of needing a PEP - they're just normal RFEs on the issue tracker.
It would seem to make sense to try and 'standardize' how this is done for the cases where it is done at all. For example, there could be some kind of collection in the exceptions (not necessarily all exceptions) that contains all the objects that participated in the operation that led to the exception. Something consistent across the different exception types would be easier to learn. For instance, with an AttributeError coming from a deeper call stack, you might want to check something like: myobj in exc.participants or myname in exc.participants This could also be more precise. For instance 'participants' might be divided into 'subjects' and 'objects', or whatever might be a suitable categorization for a broad range of different exceptions. This would need some research, though, to find the most useful naming and guidelines for these things. This might also be handy when converting from one exception type to another, because the 'participants' (or whatever they would be called) might stay the same. -- Koos -- + Koos Zevenhoven + http://twitter.com/k7hoven +
![](https://secure.gravatar.com/avatar/f3ba3ecffd20251d73749afbfa636786.jpg?s=120&d=mm&r=g)
On 7 July 2017 at 22:23, Koos Zevenhoven <k7hoven@gmail.com> wrote:
On Fri, Jul 7, 2017 at 7:28 AM, Nick Coghlan <ncoghlan@gmail.com> wrote:
As a result, our advice is to *avoid* trying to come up with systemic fixes for structured exception handling, and instead focus on specific use cases of "I want *this* exception type to have *that* attribute for *these* reasons". Those kinds of proposals usually don't even rise to the level of needing a PEP - they're just normal RFEs on the issue tracker.
It would seem to make sense to try and 'standardize' how this is done for the cases where it is done at all. For example, there could be some kind of collection in the exceptions (not necessarily all exceptions) that contains all the objects that participated in the operation that led to the exception. Something consistent across the different exception types would be easier to learn.
Potentially, but that would just require a proposal for a new created-on-first-access property on BaseException, rather than a proposal to change the constructor to implicitly populate that attribute from arbitrary keyword arguments. The functional equivalent of: @property def details(self): if "_details" in self.__dict__: details = self._details else: details = self._details = dict() return details Given something like that, the preferred API for adding details to an exception could be to do "exc.details[k] = v" rather than passing arbitrary arguments to the constructor, which would avoid most of the problems that afflicted the original "message" attribute concept. Subclasses would also be free to override the property to do something different (e.g. pre-populating the dictionary based on other exception attributes) So if folks want to explore this further, it's probably worth framing the design question in terms of a few different audiences: 1. Folks writing sys.excepthook implementations wanting to provide more useful detail to their users without overwhelming them with irrelevant local variables and without having to customise the hook for every structured exception type 2. Folks wanting to "make notes" on an exception before re-raising it 3. Folks writing new exception subclasses in Python and wanting to provide structured exception details in a consistent way 4. Folks adapting existing structured exception subclasses in Python to expose the new API 5. Folks adapting existing structured exception subclasses in C to expose the new API 6. Folks raising structured exceptions for flow control purposes who'd be annoyed by performance lost to unnecessary memory allocations Any such proposal would also need to account for any existing exception types that use "details" as the name of an ordinary instance attribute and provide a suitable deprecation period. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
![](https://secure.gravatar.com/avatar/5615a372d9866f203a22b2c437527bbb.jpg?s=120&d=mm&r=g)
On Thu, Jul 06, 2017 at 01:59:02PM -0400, Mark E. Haase wrote:
On Thu, Jul 6, 2017 at 5:58 AM, Paul Moore <p.f.moore@gmail.com> wrote:
To use the (already over-used) NameError example, Ken's proposal doesn't include any change to how NameError exceptions are raised to store the name separately on the exception.
Maybe I'm misunderstanding you, but the proposal has a clear example of raising NameError and getting the name attribute from the exception instance:
try: raise NameError(name=name, template="name '{name}' is not defined.") except NameError as e: name = e.kwargs['name'] msg = str(e) ...
What prevents the programmer from writing this? raise NameError(nym=s, template="name '{nym}' is not defined.") Or any other keyword name for that matter. Since the exception class accepts arbitrary keyword arguments, we have to expect that it could be used with arbitrary keyword arguments. Only the exception subclass knows how many and what information it expects: - NameError knows that it expects a name; - IndexError knows that it expects an index; - OSError knows that it expects anything up to five arguments (errno, errstr, winerr, filename1, filename2); etc. BaseException cannot be expected to enforce that. Ken's suggestion to put the argument handling logic in BaseException doesn't give us any way to guarantee that NameError.kwargs['name'] will even exist, or that NameError.args[0] is the name.
Yes. Because he tries to extract the name component of a NameError, and yet that component isn't stored anywhere - under his proposal or under current CPython.
I'm not sure what you mean by "extract", but the proposal calls for the name to be passed as a keyword argument (see above) and stored in self.kwargs:
class BaseException: def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs
Keyword *or positional argument*. Even if given as a keyword argument, it could use any keyword. That's a problem. Assuming it will be "name" is fragile and barely any better than the status quo, and it's harder to use than named attributes. If there is a need for NameError to make the name programmably discoverable without scraping the error message, then as PEP 352 recommends, it should be made available via an attribute: err.name, not err.args[0] or err.kwargs['name']. Here's a proof of concept of the sort of thing we could do that is backwards compatible and follows PEP 352: class NameError(Exception): def __init__(self, *args): self.args = args if len(args) == 2: self.name = args[0] else: self.name = None def __str__(self): if len(self.args) == 1: return str(self.args[0]) elif len(self.args) == 2: return "[{}] {}".format(*self.args) elif self.args: return str(self.args) return '' -- Steven
![](https://secure.gravatar.com/avatar/3312219849e076b5e99adb78b9e60b58.jpg?s=120&d=mm&r=g)
On Thu, Jul 6, 2017 at 2:56 PM, Steven D'Aprano <steve@pearwood.info> wrote:
Maybe I'm misunderstanding you, but the proposal has a clear example of raising NameError and getting the name attribute from the exception instance:
try: raise NameError(name=name, template="name '{name}' is not defined.") except NameError as e: name = e.kwargs['name'] msg = str(e) ...
What prevents the programmer from writing this?
raise NameError(nym=s, template="name '{nym}' is not defined.")
Or any other keyword name for that matter. Since the exception class accepts arbitrary keyword arguments, we have to expect that it could be used with arbitrary keyword arguments.
I agree completely with your point here, as well as the overall conclusion that the proposal to change BaseException is a bad idea. I was merely replying to [what I perceived as] straw manning of the proposal.
![](https://secure.gravatar.com/avatar/3d04c4229ca950ae9228531e3f8315b3.jpg?s=120&d=mm&r=g)
06.07.2017, 13:00, "Steven D'Aprano" <steve@pearwood.info>:
What prevents the programmer from writing this?
raise NameError(nym=s, template="name '{nym}' is not defined.")
Or any other keyword name for that matter. Since the exception class accepts arbitrary keyword arguments, we have to expect that it could be used with arbitrary keyword arguments.
Only the exception subclass knows how many and what information it expects:
- NameError knows that it expects a name; - IndexError knows that it expects an index; - OSError knows that it expects anything up to five arguments (errno, errstr, winerr, filename1, filename2);
etc. BaseException cannot be expected to enforce that. Ken's suggestion to put the argument handling logic in BaseException doesn't give us any way to guarantee that NameError.kwargs['name'] will even exist, or that NameError.args[0] is the name.
I am not understanding what is bad about your example. Yes, BaseException would allow an arbitrary set of arguments to be passed to the exception. It also defines how they would be handled by default. If people did not want that, they could subclass BaseException and replace the constructor and the __str__ method. With Ken's proposal, NameError and IndexError could then be replaced with: class NameError(Exception): '''Name not found. :param *args: args[0] contains the name that was not found. Values passed in args[1:] are ignored. :param **kwargs: kwargs['template'] is format string used to assemble an error message. This strings format() method is called with *args and **kwargs as arguments, and the result is returned by __str__(). args and kwargs are saved as attributes, so you can access the exception's arguments through them. ''' pass class IndexError(Exception): '''Sequence index out of range. :param *args: args[0] contains the value of the index. :param **kwargs: kwargs['template'] is format string used to assemble an error message. This strings format() method is called with *args and **kwargs as arguments, and the result is returned by __str__(). args and kwargs are saved as attributes, so you can access the exception's arguments through them. ''' pass OSError could be implemented by overriding the __str__() method. Once this is done, the new versions do everything the old versions do, but provide access to the components of the error message. The are also discoverable, more discoverable than the originals. In addition, they are easily extensible. For example, if I raise the NameError myself, I can provide additional useful information: try: raise NameError('welker', db='user') except NameError as e: db = e.kwargs.get('db') print('{}: {} not found.'.format(e.args[0], db) if db else str(e)) If, as you suggest, some one writes: raise NameError(nym=s, template="name '{nym}' is not defined.") it will work as expected as long as they confined themselves to using str(e). It would only fail if someone directly tried to access the argument using e.args, but that would be a very unusual thing to do and the issue could be worked around with by examining args and kwargs. Jeff
![](https://secure.gravatar.com/avatar/3d04c4229ca950ae9228531e3f8315b3.jpg?s=120&d=mm&r=g)
Paul, I am having trouble understanding your response. 06.07.2017, 03:58, "Paul Moore" <p.f.moore@gmail.com>:
On 6 July 2017 at 02:53, Jeff Walker <jeff.walker00@yandex.com> wrote:
Could you please expand on these statements:
the idea doesn't actually solve the problem it is intended to
Specifically Ken started by saying that it should not be necessary to parse the messages to get the components of the message. He then gave an example where he was able to access the components of the message without parsing the message. So how is it that he is not solving the problem he intended to solve?
Just to add my perspective here, his proposed solution (to modify BaseException) doesn't include any changes to the derived exceptions that would need to store the components. To use the (already over-used) NameError example, Ken's proposal doesn't include any change to how NameError exceptions are raised to store the name separately on the exception.
So *as the proposal stands* it doesn't allow users to extract components of any exceptions, simply because the proposal doesn't suggest changing exceptions to *store* those components.
His solution can't work
Again, he gave an example where he was able to access the components of the message without parsing the message. Yet you claim his solution cannot work. Is his example wrong?
Yes. Because he tries to extract the name component of a NameError, and yet that component isn't stored anywhere - under his proposal or under current CPython.
Here is Ken's original proposal: class BaseException: def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs def __str__(self): template = self.kwargs.get('template') if template is None: sep = self.kwargs.get('sep', ' ') return sep.join(str(a) for a in self.args) else: return template.format(*self.args, **self.kwargs) The code for storing the arguments is in the constructor. You can access the arguments through the args and kwargs attributes. This is exactly the way BaseException works currently, except that it provides no support for kwargs. Here is an example: class NameError(BaseException): pass try: raise NameError('welker', db='users', template='{0}: unknown {db}.') except NameError as e: unknown_name = e.args[0] missing_from = e.kwargs('db') print(str(e)) Given this example, please explain why it is you say that the arguments are not be stored and are not accessible.
He hasn't demonstrated that there is a real problem
You yourself admitted that parsing a message to extract the components is undesirable. Ken and others, including myself, gave examples where this was necessary. Each example was given as either being a real problem or representative of a real problem. Are we all wrong?
He's given examples of use cases. To that extent, Steven is being a touch absolute here. However, there has been some debate over whether those examples are valid. We've had multiple responses pointing out that the code examples aren't restricting what's in the try block sufficiently tightly, for example (the NameError case in particular was importing a file which, according to Ken himself, had potentially *thousands* of places where NameError could be raised). It's possible that introspecting exceptions is the right way to design a solution to this particular problem, but it does go against the normal design principles that have been discussed on this list and elsewhere many times. So, to demonstrate that there's a problem, it's necessary to address the question of whether the code could in fact have been written in a different manner that avoided the claimed problem.
People keep picking on that example, but Ken gave a reasonable justification for why it was written that way. It was a script written for a knowledgeable user. Further investment for a more 'correct' solution was neither desired nor justifiable. By dismissing the example, you dismiss a use-model for Python that is one of its strengths. It's ability to allow users to throw together powerful scripts with minimal effort. Also people seem to forget that he also pointed out that his proposal allows error messages to be trivially translated to different languages: try: raise NameError('welker') except NameError as e: print('{}: nicht gefunden.'.format(e.args[0]))
![](https://secure.gravatar.com/avatar/d995b462a98fea412efa79d17ba3787a.jpg?s=120&d=mm&r=g)
On 7 July 2017 at 04:54, Jeff Walker <jeff.walker00@yandex.com> wrote:
Here is an example:
class NameError(BaseException): pass
try: raise NameError('welker', db='users', template='{0}: unknown {db}.') except NameError as e: unknown_name = e.args[0] missing_from = e.kwargs('db') print(str(e))
Given this example, please explain why it is you say that the arguments are not be stored and are not accessible.
Because the proposal doesn't state that NameError is to be changed, and the example code isn't real, as it's manually raising a system exception. Anyway, I'm tired of this endless debate about what Ken may or may not have meant. I'm going to bow out now and restrict myself to only reading and responding to actual proposals. Paul
![](https://secure.gravatar.com/avatar/5ce43469c0402a7db8d0cf86fa49da5a.jpg?s=120&d=mm&r=g)
On 2017-07-06 02:53, Jeff Walker wrote:
Stephen, These statements do not ring true to me. I have been following the conversation closely and I have not seen support for any of them. Perhaps I missed it. Could you please expand on these statements:
the idea doesn't actually solve the problem it is intended to
Specifically Ken started by saying that it should not be necessary to parse the messages to get the components of the message. He then gave an example where he was able to access the components of the message without parsing the message. So how is it that he is not solving the problem he intended to solve?
His solution can't work
Again, he gave an example where he was able to access the components of the message without parsing the message. Yet you claim his solution cannot work. Is his example wrong?
He hasn't demonstrated that there is a real problem
You yourself admitted that parsing a message to extract the components is undesirable. Ken and others, including myself, gave examples where this was necessary. Each example was given as either being a real problem or representative of a real problem. Are we all wrong?
Sometimes you can't even parse the message. Here's an annoyance I've just come across: Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information.
my_list = ['foo', 'bar'] my_list.remove('baz') Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: list.remove(x): x not in list
If it was a KeyError, it would at least tell me what it was that was missing!
![](https://secure.gravatar.com/avatar/e2371bef92eb40cd7c586e9f2cc75cd8.jpg?s=120&d=mm&r=g)
Jeff Walker writes:
Stephen,
Mr. d'Aprano is a "Steven." I'm a "Stephen". We both go by "Steve". And you replied to Chris, not to Steve. It's worth being careful about these things. I don't see what I would consider satisfactory, concise answers to your questions about Steve's claims about defects in Ken's proposal. So I'll take a somewhat belated hack at it myself. Warning, I don't do so well on "concise", after all. ;-)
the idea doesn't actually solve the problem it is intended to
Specifically Ken started by saying that it should not be necessary to parse the messages to get the components of the message. He then gave an example where he was able to access the components of the message without parsing the message. So how is it that he is not solving the problem he intended to solve?
Changing the convention for raising NameError to
raise NameError("foo", "is undefined") Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: ('foo', 'is undefined')
provides all of the functionality of Ken's BaseException with the default template of None. Thus, it's not Ken's BaseException that solves the problem, it's the required change to the convention for raising NameError. Ken's BaseException does provide additional functionality (the template feature, allowing a pretty error message), but that's irrelevant to the actual solution of instantiating Exceptions with unformatted argument(s). Note that even
raise NameError('foo') Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: foo
isn't actually terrible in the interpreter, and would give Ken a more reliable way to extract the name (e.args[0]). Of course there's a good chance that wouldn't work so well for arbitrary Exceptions, but the multiple argument, ugly but parseable message, approach usually should work.
His solution can't work
Again, he gave an example where he was able to access the components of the message without parsing the message. Yet you claim his solution cannot work. Is his example wrong?
This claim is missing three words: "in full generality". This is demonstrated by Steven's "nym" example, which is a general problem that can occur in any raise statement. I think this indicates that you really want to provide a custom API for each and every derived Exception. Can't enforce that, or even provide a nice generic default, in BaseException as Ken proposed to revise it. There's also the rather tricky question of backward compatibility. In particular, any use of this feature in existing raise statements that involves changing the content of Exception.args will break code that parses the args rather than stringifying the Exception and parsing that. I think this breakage is likely to be very tempting when people consider "upgrading" existing raise statements with preformatted string arguments to "structured" arguments containing objects of arbitrary types. And any change to the format string (such as translating to a different language where the name appears in a different position) would imply the same breakage. Finally, while it's unfair to judge the whole proposal on proof-of- concept code, his BaseException is inadequate. Specifically, the template argument should get a decent default ("{:s}" seems reasonable, as we've seen above), and given that some arguments are likely to be strings containing spaces, his generic __str__ is going to confuse the heck out of users at the interpreter. Maybe it's really as easy as indicated here to fix those problems, but I wouldn't bet on it. To address all of the above problems, it seems reasonable to me to follow PEP 352's advice and initialize attributes on the exception from the arguments to the constructor. That plus custom __str__s to deal with nice formatting in tracebacks would make changes to BaseException unnecessary, although the addition of the template feature *might* be useful to reduce boilerplate in __str__, or even allow BaseException to provide a widely useful generic __str__. I'm doubtful it would be that widely useful, but who knows?
He hasn't demonstrated that there is a real problem
You yourself admitted that parsing a message to extract the components is undesirable.
Sure, but again two words are missing from the claim: "in BaseException". The claim is that "parse a string" is a problem in the definitions of *derived* exceptions, and in the conventions used to raise them. Programmers writing inadequate code is a real problem, but not one that Python (the language) can fix. (Of course we can submit bug reports and PEPs and fix our own inadequate code!) Another way to express this point is that we know that the raise statements will have to be fixed *everywhere*, and that some __str__s will need to be changed or added. The claim "hasn't demonstrated" is meant to say "a problem that can be fixed by changing BaseException". It seems likely to me that there are going to be a pile of slightly different ways to address this depending on the particular exception in question, and many of the more important ones (eg, OSError) are already structured and won't benefit from this change at all. Finally, generic experience has shown that the closer to root in one of these hierarchies you go, the more conservative you should be. The ramifications of a change in a very general feature for older code, as well as for more derived components, are often very hard to foresee accurately, and most surprises are unpleasant. Specifically for BaseException, Nick points out that a lot of corner cases were considered for exception hierarchies, and it was decided at that time that this kind of thing was rather risky for the foreseeable benefit. That doesn't mean it's still so risky, but I think going slow here, and doing our best to beat the proposal into a twisting, steaming hunk of half-melted metal is warranted. If it's still holding its shape after attacks with precision-guided munitions, then the risk is more likely to be worth it. :-) Steve
![](https://secure.gravatar.com/avatar/3d04c4229ca950ae9228531e3f8315b3.jpg?s=120&d=mm&r=g)
Sorry Stephen (and Steven). I'll do better next time. The way I see it there are two problems, and the second causes the first. The first problem is that there is no direct access to the components that make up the error in some of the standard Python exceptions. >>> foo Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'foo' is not defined If you need access to the name, you must de-construct the error message. To get direct access to the name, it would need to be passed to the exception when raised. Why wasn't that done? That leads us to the second problem: the base exception does not handle arguments gracefully unless you only pass an error message all by itself. For example: >>> try: >>> name = 'foo' >>> raise NameError('%s: name is not defined.' % name) >>> except NameError as e: >>> print(str(e)) foo: name is not defined. Here, printing the exception cast to a string produces a reasonable error message. >>> try: >>> name = 'foo' >>> raise NameError(name, '%s: name is not defined.' % name) >>> except NameError as e: >>> print(str(e)) ('foo', 'foo: name is not defined.') In this case, printing the exception cast to a string does not result in a reasonable error message. So the basic point is that the lack of reasonable behavior for str(e) when passing multiple arguments encourages encapsulating everything into an error message, which makes it difficult to do many useful things when handling the exceptions. Steven seems to object to the fact that the proposal takes arbitrary keyword arguments. I think this is the point of the 'nym' example. But in fact that is not really the point of the proposal, it is just a minor design choice. Instead, Steven recommends creating a custom exception with explicitly declared named arguments. And I agree with him that that is the right thing to do in many cases. But sometimes you just want something quick that you can use that does not require you to define your own exception. And sometimes, even though people should define custom exceptions, they don't. For example, NameError, KeyError, IndexError, ... -Jeff
![](https://secure.gravatar.com/avatar/d67ab5d94c2fed8ab6b727b62dc1b213.jpg?s=120&d=mm&r=g)
On Sun, Jul 16, 2017 at 10:12 AM, Jeff Walker <jeff.walker00@yandex.com> wrote:
The first problem is that there is no direct access to the components that make up the error in some of the standard Python exceptions.
>>> foo Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'foo' is not defined
If you need access to the name, you must de-construct the error message. To get direct access to the name, it would need to be passed to the exception when raised. Why wasn't that done?
Because it normally isn't needed. Can you give an example of where NameError could legitimately be raised from multiple causes? Most of the time, NameError is either being used to probe a feature (eg raw_input vs input), or indicates a bug (in which case you just let the exception get printed out as is). When do you actually need access to that name? ChrisA
![](https://secure.gravatar.com/avatar/3d04c4229ca950ae9228531e3f8315b3.jpg?s=120&d=mm&r=g)
15.07.2017, 18:33, "Chris Angelico" <rosuav@gmail.com>:
On Sun, Jul 16, 2017 at 10:12 AM, Jeff Walker <jeff.walker00@yandex.com> wrote:
The first problem is that there is no direct access to the components that make up the error in some of the standard Python exceptions.
>>> foo Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'foo' is not defined
If you need access to the name, you must de-construct the error message. To get direct access to the name, it would need to be passed to the exception when raised. Why wasn't that done?
Because it normally isn't needed. Can you give an example of where NameError could legitimately be raised from multiple causes?
For me it generally occurs when users are using Python to hold their actual data. Python can be used as a data format in much the same way JSON or Yaml. And if the end user is familiar with Python, it provides many nice benefits over the alternatives. I tend to use it in this fashion a great deal, particularly for configuration information. In these case, it would be nice to be able access the 'participants' in the following exceptions: NameError (offending name) KeyError (collection, offending key) IndexError (collection, offending index) Jeff
![](https://secure.gravatar.com/avatar/f3ba3ecffd20251d73749afbfa636786.jpg?s=120&d=mm&r=g)
On 16 July 2017 at 10:12, Jeff Walker <jeff.walker00@yandex.com> wrote:
Sorry Stephen (and Steven). I'll do better next time.
The way I see it there are two problems, and the second causes the first.
The first problem is that there is no direct access to the components that make up the error in some of the standard Python exceptions.
>>> foo Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'foo' is not defined
If you need access to the name, you must de-construct the error message. To get direct access to the name, it would need to be passed to the exception when raised. Why wasn't that done?
Ease of implementation mainly, as PyErr_Format is currently the simplest general purpose mechanism for raising informative exceptions from the C code: https://docs.python.org/3/c-api/exceptions.html#c.PyErr_Format All the more structured ones currently require expanding the C API to cover creating particular exception types with particular field values (the various PyErr_Set* functions listed on that page). That does prompt a thought, though: what if there was an f-string style "PyErr_SetStructured" API, where instead of writing 'PyErr_Format(PyExc_NameError, "name '%.200s' is not defined", name);', you could instead write 'PyErr_SetStructured(PyExc_NameError, "name '{name:.200s}' is not defined", name);', and that effectively translated to setting a NameError exception with that message *and* setting its "name" attribute appropriately. The key benefit such a switch to f-string style formatting would provide is the ability to optionally *name* the fields in the exception message if they correspond to an instance attribute on the exception being raised. Actually implementing such an API wouldn't be easy by any means (and I'm not volunteering to do it myself), but it *would* address the main practical barrier to making structured exceptions more pervasive in the reference interpreter. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
![](https://secure.gravatar.com/avatar/742dc8f125b4123c61f8e1b08c4f8a4d.jpg?s=120&d=mm&r=g)
Nick, I see. The Python C interface provides a very simple way of raising an exception in the case where the exception is only passed one argument, the error message. It even makes it easy to interpolate arguments into that error message. If it is important to include other arguments, as with OSError, one would presumably use another mechanism that requires more effort. Your suggestion of providing an alternative to PyErr_Format() that squirreled away its arguments into the exception and deferred their interpolation into the error message seems like a very nice approach. But as you say, it does not seem trivial to implement in C. If it could be extended to support named arguments, it does have the benefit that it could unify the way exceptions are raise from C. The alternative would be to simply enhance the individual exceptions on an as needed basis as you suggested earlier. That could be easy if the exceptions are only raised in one or two places. Do you have a sense for how many places raise some of these common exceptions such as NameError, KeyError, etc.? -Ken On Sun, Jul 16, 2017 at 02:18:40PM +1000, Nick Coghlan wrote:
On 16 July 2017 at 10:12, Jeff Walker <jeff.walker00@yandex.com> wrote:
Sorry Stephen (and Steven). I'll do better next time.
The way I see it there are two problems, and the second causes the first.
The first problem is that there is no direct access to the components that make up the error in some of the standard Python exceptions.
>>> foo Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'foo' is not defined
If you need access to the name, you must de-construct the error message. To get direct access to the name, it would need to be passed to the exception when raised. Why wasn't that done?
Ease of implementation mainly, as PyErr_Format is currently the simplest general purpose mechanism for raising informative exceptions from the C code: https://docs.python.org/3/c-api/exceptions.html#c.PyErr_Format
All the more structured ones currently require expanding the C API to cover creating particular exception types with particular field values (the various PyErr_Set* functions listed on that page).
That does prompt a thought, though: what if there was an f-string style "PyErr_SetStructured" API, where instead of writing 'PyErr_Format(PyExc_NameError, "name '%.200s' is not defined", name);', you could instead write 'PyErr_SetStructured(PyExc_NameError, "name '{name:.200s}' is not defined", name);', and that effectively translated to setting a NameError exception with that message *and* setting its "name" attribute appropriately.
The key benefit such a switch to f-string style formatting would provide is the ability to optionally *name* the fields in the exception message if they correspond to an instance attribute on the exception being raised.
Actually implementing such an API wouldn't be easy by any means (and I'm not volunteering to do it myself), but it *would* address the main practical barrier to making structured exceptions more pervasive in the reference interpreter.
Cheers, Nick.
![](https://secure.gravatar.com/avatar/f3ba3ecffd20251d73749afbfa636786.jpg?s=120&d=mm&r=g)
On 17 July 2017 at 04:59, Ken Kundert <python-ideas@shalmirane.com> wrote:
Nick, I see. The Python C interface provides a very simple way of raising an exception in the case where the exception is only passed one argument, the error message. It even makes it easy to interpolate arguments into that error message. If it is important to include other arguments, as with OSError, one would presumably use another mechanism that requires more effort.
Your suggestion of providing an alternative to PyErr_Format() that squirreled away its arguments into the exception and deferred their interpolation into the error message seems like a very nice approach. But as you say, it does not seem trivial to implement in C. If it could be extended to support named arguments, it does have the benefit that it could unify the way exceptions are raise from C.
I'll note that while it wouldn't be trivial, it *should* at least theoretically be possible to share the string segmentation and processing steps with the f-string implementation. However, I also realised that a much simpler approach to the same idea would instead look like: PyErr_SetFromAttributes(PyExc_NameError, "name '%.200s' is not defined", "name", name); Where the field names alternate with the field values in the argument list, rather than being embedded in the format string. Ignoring error handling, the implementation would then be something like: - split va_list into a list of field names and a list of field values - set the exception via `PyErr_SetObject(exc_type, PyUnicode_Format(exc_msg, field_values))` - retrieve the just set exception and do `PyObject_SetAttr(exc, field_name, field_value)` for each field with a non-NULL name
The alternative would be to simply enhance the individual exceptions on an as needed basis as you suggested earlier. That could be easy if the exceptions are only raised in one or two places. Do you have a sense for how many places raise some of these common exceptions such as NameError, KeyError, etc.?
A quick check shows around a dozen hits for PyExc_NameError in c files (all in ceval.c), and most of the hits for NameError in Python files being related to catching it rather than raising it. PyExc_KeyError is mentioned in a dozen or so C files (most of them raising it, a few of them check for it being raised elsewhere), and while the hits in Python files still mostly favour catching it, it's raised explicitly in the standard library more often than NameError is. The nice thing about the PyErr_SetFromAttributes approach at the C API level is that it pairs nicely with the following approach at the constructor level: class NameError(Exception): def __init__(self, message, *, name=None): super().__init__(message) self.name = name Something like KeyError may want to distinguish between "key value was None" and "key value wasn't reported when raising the exception" (by not defining the attribute at all in the latter case), but PyErr_SetFromAttributes wouldn't need to care about those kinds of details. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
![](https://secure.gravatar.com/avatar/e2371bef92eb40cd7c586e9f2cc75cd8.jpg?s=120&d=mm&r=g)
First, I would like to make it clear that I am not claiming that the current design of standard Exceptions cannot be improved, nor do I necessarily have a problem with doing it at the BaseException level. My argument has always been directed to the issue of "does changing BaseException actually save work in design and implementation of the interpreter, of standard derived Exceptions, and of new user Exceptions?" I don't think it does. But Ken and you have taken opposition to Ken's proposal to indicate that we don't see a problem with the current implementation of the standard Exception hierarchy. That's not true. We do understand the problems. We differ on importance: Steven d'A seems to deprecate the NameError in "data-sets-formatted-as-Python-containers" problem as rare and only encountered by a few users. I think that's a big enough class of use cases (I do it myself, though I've never encountered Ken's use case) to worry about, though I wouldn't be willing to do the work myself on the evidence so far presented. But we do understand. What you and Ken haven't done yet is to show 1. the implementation of an improvement is simple and generic, and 2. enough so to justify what seems to be an improvement restricted to an uncommon set of use cases. Nick has helped to convince me that (1) can be addressed, though he warned that it's not all that easy (especially if you're a DRY fanatic). The question then is the balance between (1) and (2). Jeff Walker writes:
The first problem is that there is no direct access to the components that make up the error in some of the standard Python exceptions.
That's not a problem in BaseException, though. BaseException allows access to arbitrary Python objects, if, and only if, the developer puts them in the exception.
Why wasn't that done?
I don't know, but it's not prevented by BaseException.
That leads us to the second problem: the base exception does not handle arguments gracefully unless you only pass an error message all by itself.
I think it would have been a mistake to try to do something graceful. The point is that looking at the design, I would suppose that BaseException had only one job: conveying whatever information the programmer chose to put in it faithfully to an exception handler. It does that, even if you hand it something really wierd, and in several pieces. This is called "humility in design when you have no idea what you're designing for". The Zen expresses it "But sometimes never is better than *right* now." It's easy to design a better BaseException for a handful of cases. It's not at all clear that such a BaseException would work well for more than a double handful. If it doesn't, it would tend to cause the same problem for exceptions that don't fit it very well but the programmer doesn't think are important enough to deserve a well-defined derived Exception. Since it would necessarily be more complex than the current BaseException, it might have even uglier failure modes.
>>> try: >>> name = 'foo' >>> raise NameError(name, '%s: name is not defined.' % name)
l > >>> except NameError as e:
>>> print(str(e)) ('foo', 'foo: name is not defined.')
In this case, printing the exception cast to a string does not result in a reasonable error message.
Sure, but that's because the error message was poorly chosen. I would raise NameError(name, 'error: undefined name') which would stringify as ('foo', 'error: undefined name') (and there are probably even better ways to phrase that). But in the case of NameError, I don't understand why a string is used at all. I really don't see a problem with the interpreter printing NameError: foo and a traceback indicating where "foo" was encountered, and having programmatic handlers do something appropriate with e.args[0]. It's not obvious to me that the same is not true of most of the "stupid string formatting" Exceptions that have been given as examples.
So the basic point is that the lack of reasonable behavior for str(e)
I hate to tell you this, but str doesn't guarantee anything like reasonable behavior for error handling. Even repr doesn't guarantee you can identify an object passed to it, nor reproduce it. I have to assume that the developers who designed these Exceptions believed that in the vast majority of cases the mere fact that an instance of particular Exception was raised is enough to identify the offending object. Perhaps they were mistaken. But if they were, the places in the interpreter where they are raised and handled all must be be checked to see if they need to be changed, whether we change BaseException or the individual derived Exceptions. And if we change BaseException, I expect it will be very desirable to change individual derived Exceptions as well (to give them intelligible error message templates). Also, it's not obvious to me that default templates are such a useful idea. I almost always use unique messages when "printf debugging", out of habit developed in other languages. That means I rarely have to look at tracebacks for simple errors. If better templates make it easier for people to not use context-specific messages, when they are warranted that's not a win, I think.
But in fact [use of keyword arguments to set "advanced" formatting] is not really the point of the proposal, it is just a minor design choice.
I don't think it's so minor. The keyword argument approach was apparently chosen to avoid breaking backward compatibility in BaseException. Compatibility break is not an option here, IMO. I think it's also an important part of Ken's proposal that this be an "easy" tweak that makes it easier to write better derived Exceptions and better exception handling in many cases.
But sometimes you just want something quick that you can use that does not require you to define your own exception. And sometimes, even though people should define custom exceptions, they don't. For example, NameError, KeyError, IndexError, ...
I don't see how that is an argument for the proposed change to BaseException. Steve
![](https://secure.gravatar.com/avatar/3312219849e076b5e99adb78b9e60b58.jpg?s=120&d=mm&r=g)
On Wed, Jul 5, 2017 at 9:22 PM, Chris Angelico <rosuav@gmail.com> wrote:
For what it's worth, I'm in favour of Steven's "too negative" approach - or rather, I don't think his style is too negative. Yes, it's a bit rough and uncomfortable to be on the receiving end of it, but it's exactly correct. All three of the statements you quote are either provably true from the emails in this thread, or are at least plausible. If you think he's wrong to say them, *say so*, and ask him to justify them.
Perhaps what we need is a "falsehoods programmers believe about python-ideas" collection. I'll start it:
* All ideas are worthy of respect. * My use-case is enough justification for adding something to the language. * Criticism is bad. Ideas should be welcomed just because they're ideas. ...snip...
I don't think Ken actually made any of the false assumptions you've listed here, so it's a bit harsh to post that list in this thread. This list is for "speculative language ideas" and "discussion". Ken has met that standard. The topic of tone is interesting, and a broader discussion of how to use python-ideas for newcomers and regulars alike is probably overdue, just not in this thread.
![](https://secure.gravatar.com/avatar/d67ab5d94c2fed8ab6b727b62dc1b213.jpg?s=120&d=mm&r=g)
On Fri, Jul 7, 2017 at 3:30 AM, Mark E. Haase <mehaase@gmail.com> wrote:
On Wed, Jul 5, 2017 at 9:22 PM, Chris Angelico <rosuav@gmail.com> wrote:
For what it's worth, I'm in favour of Steven's "too negative" approach - or rather, I don't think his style is too negative. Yes, it's a bit rough and uncomfortable to be on the receiving end of it, but it's exactly correct. All three of the statements you quote are either provably true from the emails in this thread, or are at least plausible. If you think he's wrong to say them, *say so*, and ask him to justify them.
Perhaps what we need is a "falsehoods programmers believe about python-ideas" collection. I'll start it:
* All ideas are worthy of respect. * My use-case is enough justification for adding something to the language. * Criticism is bad. Ideas should be welcomed just because they're ideas. ...snip...
I don't think Ken actually made any of the false assumptions you've listed here, so it's a bit harsh to post that list in this thread. This list is for "speculative language ideas" and "discussion". Ken has met that standard.
The topic of tone is interesting, and a broader discussion of how to use python-ideas for newcomers and regulars alike is probably overdue, just not in this thread.
I didn't intend to imply that any one person had made any particular assumptions. But if a list like this could be published somewhere, it would help people to realise what they're unintentionally implying. ChrisA
![](https://secure.gravatar.com/avatar/5615a372d9866f203a22b2c437527bbb.jpg?s=120&d=mm&r=g)
On Thu, Jul 06, 2017 at 03:32:21AM +1000, Steven D'Aprano wrote:
On Wed, Jul 05, 2017 at 04:12:29PM +0000, Ed Kellett wrote:
Hi,
On Wed, 5 Jul 2017 at 16:41 Steven D'Aprano <steve@pearwood.info> wrote:
and more. Third parties *are* providing rich exception APIs where it makes sense to do so, using the interface encouraged by PEP 352 (named attributes), without needing a default "StructuredException" in the core language.
Your arguments might be used to dismiss anything.
Do you have an answer for why the argument is wrong? People *are* writing structured exceptions, which undercuts the argument that we must do something because if we don't lead the way others won't. [...]
Apologies, I hit the wrong key intending to save the email for later but accidentally hit send instead, so the end of the post may be a bit (or a lot) incoherent. -- Steve
![](https://secure.gravatar.com/avatar/95fb3c56e2e5322b0f9737fbb1eb9bce.jpg?s=120&d=mm&r=g)
On 2017-07-05 04:36, Steven D'Aprano wrote:
On Tue, Jul 04, 2017 at 11:37:51PM -0400, Terry Reedy wrote:
I personally been on the side of wanting richer exceptions.
Could you explain what you would use them for? Ken has give two use-cases which I personally consider are relatively niche, and perhaps even counter-productive:
- translation into the user's native language;
- providing some sort of "did you mean...?" functionality.
I'm not the one you were replying to, but I just consider it useful debugging information that reduces the work required to track down errors. Suppose I have code like this: for item in blah: do_stuff(item) def do_stuff(item): foo = item['bar'] do_other_stuff(foo) # etc., then 10 functions down the call stack. . . def do_yet_additional_stuff(whatever): x = whatever['yadda'] Now I get a KeyError exception in do_yet_additional_stuff. If the object on which the key was not found ("whatever" here) is stored as an attribute of the exception, I can put a try/except in any function in the call stack and still have access to the object that caused the exception further down. This makes it easier to try out hypotheses about what's causing the error. ("Hmmm, I know that in do_stuff it reads the 'bar', attribute, maybe if that value is negative it's resulting in a NaN later on. . .") If the object is not available as an exception attribute, I have to start by going all the way to the bottom of the call stack (to do_yet_additional_stuff) and either creating my own custom exception that does store the object (thus implementing this attribute-rich exception myself) or slowly working my way back up the call stack. Having the object available on the exception is like having your finger in the pages of a book to hold the place where the exception actually occurred, while still being able to flip back and forth to other sections of the call stack to try to figure out how they're leading to that exception. A related use is when the exception is already caught and logged or handled somewhere higher up. It makes it easier to log messages like "AttributeError was raised when foo had item=-10.345", or to conditionally handle exceptions based on such information, without requiring extra instrumentation elsewhere in the code. I realize this isn't a life-and-death use case, but to be frank I don't really see that the objections are that strong either. So far I've seen "You don't really need that information", "You don't need that information if you're using exceptions for flow control" and "There might theoretically be a performance penalty of unknown significance". (I seem to recall seeing something like "exceptions aren't supposed to be debugging tools" in an earlier discussion about this, and I basically just disagree with that one.) Now, obviously there is the generic burden-of-proof argument that the suggestion doesn't meet the bar for changing the status quo, and that's as may be, but that's different from there being no use case. It also doesn't necessarily rule out a potential resolution like "okay, this isn't urgent, but let's try to clean this up gradually as we move forward, by adding useful info to exceptions where possible". That said, I think this use case of mine is on a different track from where most of the discussion in this thread seems to have gone. For one thing, I agree with you that NameError is a particularly odd exception to pick as the poster child for rich exceptions, because NameError is almost always raised in response to a variable name that's typed literally in the code, so it's much less likely to result in the kind of situation I described above, where the true cause of an exception is far away in the call stack from the point where it's raised. I also agree that it is much better to have the information available as named attributes on the exception rather than accessing them positionally with something like exception.args[1]. In fact I agree so much that basically what I'm saying is that all exceptions as much as possible should store relevant information in such attributes rather than putting it ONLY into the message string. Also, in the situation I'm describing, I'm not particularly attached to the idea that the "rich" information would even have to be part of the exception message at all by default (since the object might have a long __str__ that would be irritating). It would just be there, attached to the exception, so that it could be used if needed. -- Brendan Barnwell "Do not follow where the path may lead. Go, instead, where there is no path, and leave a trail." --author unknown
![](https://secure.gravatar.com/avatar/72ee673975357d43d79069ac1cd6abda.jpg?s=120&d=mm&r=g)
Terry Reedy wrote:
Attaching a *constant* string is very fast, to the consternation of people who would like the index reported.
Seems to me that storing the index as an attribute would help with this. It shouldn't be much slower than storing a constant string, and formatting the message would be deferred until it's needed, if at all. -- Greg
![](https://secure.gravatar.com/avatar/d6b9415353e04ffa6de5a8f3aaea0553.jpg?s=120&d=mm&r=g)
On 7/4/2017 6:31 PM, Greg Ewing wrote:
Terry Reedy wrote:
Attaching a *constant* string is very fast, to the consternation of people who would like the index reported.
Actually, the constant string should be attached to the class, so there is no time needed.
Seems to me that storing the index as an attribute would help with this. It shouldn't be much slower than storing a constant string,
Given that the offending int is available as a Python int, then storing a reference should be quick, though slower than 0 (see above ;-).
and formatting the message would be deferred until it's needed, if at all.
I agree that this would be the way to do it. I will let an advocate of this enhancement lookup the rejected issue (there may be more than one) proposing to make the bad index available and see if this is the actual proposal rejected and if so, why (better than I may remember). It occurs to me that if the exception object has no reference to any python object, then all would be identical and only one cached instance should be needed. I checked and neither IndexError nor ZeroDivisionError do this. -- Terry Jan Reedy
![](https://secure.gravatar.com/avatar/d6b9415353e04ffa6de5a8f3aaea0553.jpg?s=120&d=mm&r=g)
On 7/5/2017 12:21 AM, Terry Reedy wrote:
On 7/4/2017 6:31 PM, Greg Ewing wrote:
Terry Reedy wrote:
Attaching a *constant* string is very fast, to the consternation of people who would like the index reported.
Actually, the constant string should be attached to the class, so there is no time needed.
I should say, a default string, or parameter default value. I just checked and Python gives the numerator type the ZeroDivisionError message and the sequence type in the IndexError message, so scratch that idea.
Seems to me that storing the index as an attribute would help with this. It shouldn't be much slower than storing a constant string,
Given that the offending int is available as a Python int, then storing a reference should be quick, though slower than 0 (see above ;-).
0 is wrong. Just a reference storage in both cases.
and formatting the message would be deferred until it's needed, if at all.
I agree that this would be the way to do it. I will let an advocate of this enhancement lookup the rejected issue (there may be more than one) proposing to make the bad index available and see if this is the actual proposal rejected and if so, why (better than I may remember).
It occurs to me that if the exception object has no reference to any python object, then all would be identical and only one cached instance should be needed. I checked and neither IndexError nor ZeroDivisionError do this.
-- Terry Jan Reedy
![](https://secure.gravatar.com/avatar/5f0c5d9d5e30666e382dc05ea80e92f8.jpg?s=120&d=mm&r=g)
I agree with Ken for the need to make rich exceptions easy to write, but I do not know enough to say if changing BaseException to support this is a good idea; I made my own error reporting library to do this: For example, to define a new exception type with a `name` attribute: raise Log.error("name {{name|quote}} is not defined.", name=name) My point is that the first parameter, the template string, acts as the class definition. Now, there are some extra "features": The mustaches demand named parameters, and use pipe for some simple formatting hints; I hope you can overlook that and maybe use f-strings, or similar. The template string is not expanded unless the text log is required; the errors serialize nicely to JSON for a structured logging system; querying for instances of this error is the same as looking for the template string. My library does not solve the problem of extracting parameters out of the standard lib errors; but catching them early, chaining, and properly classing them, is good enough. >>> try: ... a={} # imagine this is something complicated ... c = a.b ... except Exception as e: ... Log.error("can not do something complicated", cause=e) # just like raise from, plus we are defining a new exception type On 2017-07-05 00:21, Terry Reedy wrote:
On 7/4/2017 6:31 PM, Greg Ewing wrote:
Terry Reedy wrote:
Attaching a *constant* string is very fast, to the consternation of people who would like the index reported.
Actually, the constant string should be attached to the class, so there is no time needed.
Seems to me that storing the index as an attribute would help with this. It shouldn't be much slower than storing a constant string,
Given that the offending int is available as a Python int, then storing a reference should be quick, though slower than 0 (see above ;-).
and formatting the message would be deferred until it's needed, if at all.
I agree that this would be the way to do it. I will let an advocate of this enhancement lookup the rejected issue (there may be more than one) proposing to make the bad index available and see if this is the actual proposal rejected and if so, why (better than I may remember).
It occurs to me that if the exception object has no reference to any python object, then all would be identical and only one cached instance should be needed. I checked and neither IndexError nor ZeroDivisionError do this.
![](https://secure.gravatar.com/avatar/742dc8f125b4123c61f8e1b08c4f8a4d.jpg?s=120&d=mm&r=g)
On Tue, Jul 04, 2017 at 04:54:11PM -0400, Terry Reedy wrote:
There have been many proposals for what we might call RichExceptions, with more easily access information. But as Raymond Hettinger keeps pointing out, Python does not use exceptions only for (hopefully rare) errors. It also uses them as signals for flow control, both as an alternative form for alternation and for iteration. Alternation with try:except instead of if:else is common. In the try: unicode example above, the NameError is not an error. Until 2.2, IndexError served the role of StopIteration today, and can still be used for iteration. For flow control, richer exceptions just slow code execution.
Terry, Correct me if I am wrong, but this seems like an argument for the proposal. Consider the NameError, currently when raised the error message must be constructed before it is passed to the exception. But in the proposal, you simply pass the name (already available) and the format string (a constant). The name is never interpolated into the format string unless the message is actually used, which it would not in the cases you cite. -Ken
![](https://secure.gravatar.com/avatar/5615a372d9866f203a22b2c437527bbb.jpg?s=120&d=mm&r=g)
On Tue, Jul 04, 2017 at 05:08:57PM -0700, Ken Kundert wrote:
On Tue, Jul 04, 2017 at 04:54:11PM -0400, Terry Reedy wrote:
There have been many proposals for what we might call RichExceptions, with more easily access information. But as Raymond Hettinger keeps pointing out, Python does not use exceptions only for (hopefully rare) errors. It also uses them as signals for flow control, both as an alternative form for alternation and for iteration. Alternation with try:except instead of if:else is common. In the try: unicode example above, the NameError is not an error. Until 2.2, IndexError served the role of StopIteration today, and can still be used for iteration. For flow control, richer exceptions just slow code execution.
Terry, Correct me if I am wrong, but this seems like an argument for the proposal. Consider the NameError, currently when raised the error message must be constructed before it is passed to the exception. But in the proposal, you simply pass the name (already available) and the format string (a constant). The name is never interpolated into the format string unless the message is actually used, which it would not in the cases you cite.
Terry's argument is that when used for flow control, you don't care what the index is. You just raise IndexError("index out of bounds") or similar. Or for that matter, just raise IndexError(), as generators usually raise StopIteration() with no arguments. -- Steve
![](https://secure.gravatar.com/avatar/d6b9415353e04ffa6de5a8f3aaea0553.jpg?s=120&d=mm&r=g)
On 7/4/2017 8:08 PM, Ken Kundert wrote:
On Tue, Jul 04, 2017 at 04:54:11PM -0400, Terry Reedy wrote:
There have been many proposals for what we might call RichExceptions, with more easily access information. But as Raymond Hettinger keeps pointing out, Python does not use exceptions only for (hopefully rare) errors. It also uses them as signals for flow control, both as an alternative form for alternation and for iteration. Alternation with try:except instead of if:else is common. In the try: unicode example above, the NameError is not an error. Until 2.2, IndexError served the role of StopIteration today, and can still be used for iteration. For flow control, richer exceptions just slow code execution.
Terry, Correct me if I am wrong, but this seems like an argument for the proposal.
I actually do not know what 'the proposal' is and how it is the same or different from past proposals, especially those that have been rejected. I initially elaborated on some points of Steven D'Aprano that I agree with, in light of past discussions and tracker issues.
Consider the NameError, currently when raised the error message must be constructed before it is passed to the exception. But in the proposal, you simply pass the name (already available) and the format string (a constant). The name is never interpolated into the format string unless the message is actually used, which it would not in the cases you cite.
That is close to what I am thinking. I would give the format a default value, the one Python uses most often. def NameError(name, template="name {name} not found"): self.name = name self.template = template -- Terry Jan Reedy
![](https://secure.gravatar.com/avatar/5615a372d9866f203a22b2c437527bbb.jpg?s=120&d=mm&r=g)
On Sun, Jul 02, 2017 at 12:19:54PM -0700, Ken Kundert wrote:
class BaseException: def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs
def __str__(self): template = self.kwargs.get('template') if template is None: sep = self.kwargs.get('sep', ' ') return sep.join(str(a) for a in self.args) else: return template.format(*self.args, **self.kwargs)
I think this API is too general. It accepts arbitrary keyword arguments and stores them in the exception. I think that makes for a poor experience for the caller: try: something() except MyParsingError as err: if 'column' in err.kwargs: ... elif 'col' in err.kwargs: ... elif 'x' in err.kwargs: # 'x' is the column, or is it the row? ... The problem here is, unless the exception class offers a real API for extracting the column, how do you know what key to use? You can't expect BaseException to force the user of MyParsingError to be consistent: raise MyParsingError(17, 45, template='Error at line {} column {}') raise MyParsingError(45, 17, template='Error at column {} line {}') raise MyParsingError(45, 17, temlpate='Error at col {} row {}') # oops raise MyParsingError(17, template='Error at line {}') raise MyParsingError(99, 45, 17, template='Error code {} at column {} line {}') Unless MyParsingError offers a consistent (preferably using named attributes) API for extracting the data, pulling it out of args is just as much of a hack as scraping it from the error message. It seems to me that we can't solve this problem at BaseException. It has to be tackled by each exception class. Only the author of each exception class knows what information it carries and should be provided via named attributes. I think OSError has a good approach. https://docs.python.org/3/library/exceptions.html#OSError For backwards compatibility, when you raise OSError with a single argument, it looks like this: raise OSError('spam is my problem') => OSError: spam is my problem When you offer two or up to five arguments, they get formatted into a nicer error message, and stored into named attributes: raise OSError(99, 'spam is my problem', 'xxx', 123, 'yyy') => OSError: [Errno 99] foo is my problem: 'xxx' -> 'yyy' Missing arguments default to None. I don't think there is a generic recipe that will work for all exceptions, or even all exceptions in the standard exception heirarchy, that can make that pleasant. Perhaps I'm insufficiently imaginative, but I don't think this problem can be solved with a quick hack of the BaseException class. -- Steve
participants (19)
-
Brendan Barnwell
-
Chris Angelico
-
David Mertz
-
Ed Kellett
-
Greg Ewing
-
Jeff Walker
-
Juancarlo Añez
-
Ken Kundert
-
Koos Zevenhoven
-
Kyle Lahnakoski
-
Mark E. Haase
-
Matthias Bussonnier
-
MRAB
-
Nick Coghlan
-
Paul Moore
-
Peter Otten
-
Stephen J. Turnbull
-
Steven D'Aprano
-
Terry Reedy