So the following code:
>>> d = {}
>>> try:
... val = d['nosuch']
... except:
... raise AttributeError("No attribute 'nosuch'")
...
Give the traceback I expected and wanted in Python 2:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
AttributeError: No attribute 'nosuch'
but in Python 3.1 the traceback looks like this:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
KeyError: 'nosuch'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
AttributeError: No attribute 'nosuch'
Modifying the code a little allows me to change the error message, but
not much else:
>>> d = {}
>>> try:
... val = d['nosuch']
... except KeyError as e:
... raise AttributeError("No attribute 'nosuch'") from e
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
KeyError: 'nosuch'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
AttributeError: No attribute 'nosuch'
>>>
In a class's __getattr__() method this means that instead of being able
to say
try:
value = _attrs[name]
except KeyError:
raise AttributeError ...
I am forced to write
if name not in _attrs:
raise AttributeError ...
value = _attrs[name]
which requires an unnecessary second lookup on the attribute name. What
is the correct paradigm for this situation?
regards
Steve
--
Steve Holden +1 571 484 6266 +1 800 494 3119
PyCon 2011 Atlanta March 9-17 http://us.pycon.org/
See Python Video! http://python.mirocommunity.org/
Holden Web LLC http://www.holdenweb.com/
There doesn't seem to be one at the moment, although the issue isn't
very serious. Your Traceback is merely being made slightly longer/more
complicated than you'd prefer; however, conversely, what if a bug was
to be introduced into your exception handler? Then you'd likely very
much appreciate the "superfluous" Traceback info.
Your quandary is due to the unresolved status of the "Open Issue:
Suppressing Context" in PEP 3141
(http://www.python.org/dev/peps/pep-3134/ ). I guess you could start a
discussion about closing that issue somehow.
Cheers,
Chris
--
http://blog.rebertia.com
You're right about the issue not being serious, (and about the possible
solution, though I don't relish a lengthy discussion on python-dev) but
it does seem that there ought to be some way to suppress that
__context__. From the user's point of view the fact that I am raising
AttributeError because of some implementation detail of __getattr__() is
exposing *way* too much information.
I even tried calling sys.exc_clear(), but alas that doesn't help :(
> I was somewhat surprised to discover that Python 3 no longer allows an
> exception to be raised in an except clause (or rather that it reports it
> as a separate exception that occurred during the handling of the first).
So what exactly is the problem? Exceptions are so easy to get wrong, it’s
just trying to report more info in a complicated situation to help you track
down the problem. Why is that bad?
> In a class's __getattr__() method this means that instead of being able
> to say
>
> try:
> value = _attrs[name]
> except KeyError:
> raise AttributeError ...
>
> I am forced to write
>
> if name not in _attrs:
> raise AttributeError ...
> value = _attrs[name]
I don’t see why. Presumably if you caught the exception in an outer try-
except clause, you would pick up your AttributeError, not the KeyError,
right? Which is what you want, right?
I think you are misinterpreting what you are seeing. The exception being
raised actually *is* an attribute error, and it actually is the
attribute error that gets reported. It's only that reporting an
exception that has a __context__ first reports the context, then reports
the actual exception.
You may now wonder whether it is possible to set __context__ to None
somehow. See PEP 3134:
Open Issue: Suppressing Context
As written, this PEP makes it impossible to suppress '__context__',
since setting exc.__context__ to None in an 'except' or 'finally'
clause will only result in it being set again when exc is raised.
Regards,
Martin
> On 10/24/2010 1:26 AM, Chris Rebert wrote:
>>> I was somewhat surprised to discover that Python 3 no longer allows an
>>> > exception to be raised in an except clause (or rather that it reports
>>> > it as a separate exception that occurred during the handling of the
>>> > first).
>> <snip>
> [snip]
>>> > What
>>> > is the correct paradigm for this situation?
>> There doesn't seem to be one at the moment, although the issue isn't
>> very serious. Your Traceback is merely being made slightly longer/more
>> complicated than you'd prefer; however, conversely, what if a bug was
>> to be introduced into your exception handler? Then you'd likely very
>> much appreciate the "superfluous" Traceback info.
>>
>> Your quandary is due to the unresolved status of the "Open Issue:
>> Suppressing Context" in PEP 3141
>> (http://www.python.org/dev/peps/pep-3134/ ). I guess you could start a
>> discussion about closing that issue somehow.
>
> You're right about the issue not being serious, (and about the possible
> solution, though I don't relish a lengthy discussion on python-dev) but
> it does seem that there ought to be some way to suppress that
> __context__. From the user's point of view the fact that I am raising
> AttributeError because of some implementation detail of __getattr__() is
> exposing *way* too much information.
>
> I even tried calling sys.exc_clear(), but alas that doesn't help :(
You can install a custom excepthook:
>>> import traceback, sys
>>> from functools import partial
>>> def f():
... try: 1/0
... except: raise AttributeError
...
>>> f()
Traceback (most recent call last):
File "<stdin>", line 2, in f
ZeroDivisionError: int division or modulo by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in f
AttributeError
>>> sys.excepthook = partial(traceback.print_exception, chain=False)
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in f
AttributeError
Peter
This behavior is quite reasonable during testing, but I would prefer to
exclude an explicit raise directly in the except handler since that is
hardly to be construed as accidental (whereas an exception in a function
called in the handler perhaps *should* be reported).
> You may now wonder whether it is possible to set __context__ to None
> somehow. See PEP 3134:
>
> Open Issue: Suppressing Context
>
> As written, this PEP makes it impossible to suppress '__context__',
> since setting exc.__context__ to None in an 'except' or 'finally'
> clause will only result in it being set again when exc is raised.
>
I have already read that. Peter Otten has separately explained how to
suppress the behavior using sys.excepthook, which appears to be a
halfway satisfactory solution.
Yes, *if the exception is caught* then it doesn't make any difference.
If the exception creates a traceback, however, I maintain that the
additional information is confusing to the consumer (while helpful to
the debugger of the consumed code).
I don't want people to think this is a big deal, however. It was just an
"eh?" that I thought must mean I was missing some way of suppressing the
additional traceback. Peter Otten has already provided a solution using
sys.except_hook().
FYI, Java has a similar behavior. In Java, however, after a certain
length, some of the older exceptions will be suppressed and will only
print message informing that there are more exceptions above it.
This is a traceback issue only, right? The semantics of
the code below shouldn't change in Py3.x, I hope:
try :
...
try :
x = 1/0 # in a real program, input data might cause a problem
except ZeroDivisionError as msg:
raise RuntimeError("Math error on problem: " + str(msg))
except RuntimeError as msg :
print("Trouble: " + str(msg))
I have code where I'm reading and parsing a web page,
a process which can produce a wide range of errors. A try-block
around the read and parse catches the various errors and creates a
single user-defined "bad web page" exception object, which is then
raised. That gets caught further out, and is used to record the
troubled web page, schedule it for a retest, and such. This is
normal program operation, indicative of external problems, not a
code error or cause for program termination with a traceback.
Are exception semantics changing in a way which would affect that?
John Nagle
No, I don't believe so. I simply felt that the traceback gives too much
information in the case where an exception is specifically being raised
to replace the one currently being handled.
> I simply felt that the traceback gives too much information in the
> case where an exception is specifically being raised to replace the
> one currently being handled.
Ideally, that description of the problem would suggest the obvious
solution: replace the class of the exception and allow the object to
continue up the exception handler stack.
But that doesn't work either::
>>> d = {}
>>> try:
... val = d['nosuch']
... except KeyError as exc:
... exc.__class__ = AttributeError
... raise exc
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
KeyError: 'nosuch'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
TypeError: __class__ assignment: only for heap types
which means, AFAICT, that re-binding ‘__class__’ is only allowed for
objects of a type defined in the Python run-time heap, not those defined
in C code (like the built-in-exception types).
--
\ “I wish there was a knob on the TV to turn up the intelligence. |
`\ There's a knob called ‘brightness’ but it doesn't work.” |
_o__) —Eugene P. Gallagher |
Ben Finney
> Yes, *if the exception is caught* then it doesn't make any difference.
> If the exception creates a traceback, however, I maintain that the
> additional information is confusing to the consumer (while helpful to
> the debugger of the consumed code).
Who needs the information more?
I think you have puzzled readers a lot (including me) with the statement:
"that Python 3 no longer allows an exception to be raised in an except
clause"
That certainly isn't the case.
Regards,
Martin
> (or rather that it reports it as a separate exception that occurred
> during the handling of the first)
I understand that this behavior is deliberate. I just don't feel that it
is universally helpful.
> Yeah, that's a given. Ruby would probably let you do that, but Python
> insists that you don't dick around with the built-in types. And roghtly
> so, IMHO.
Some restrictions on this are necessary -- it obviously
wouldn't be safe to allow replacing the class of an
object with one having an incompatible C layout, and
most built-in types have their own unique layout.
I think it's easier in Ruby, because all objects in
Ruby look pretty much the same at the C level. Not
sure of the details, though.
--
Greg
I think what's disturbing about this is that the two halves of
the extended traceback are printed in the wrong order. We're
all used to looking down the bottom of the traceback to see
where the error originated, but with the new format, that point
is buried somewhere in the middle.
--
Greg
True, but swapping the order would only worsen Steve's problem. Most
of his users presumably won't care about the underlying KeyError and
would rather be presented with the AttributeError as the proximate
"origin", despite that being technically inaccurate in the way you
suggest. Six of one, half dozen of the other though.
Cheers,
Chris
It is not easily discoverable, but it is possible to suppress
__context__ by using a bare re-raise afterwards:
>>> try:
... try: 1/0
... except ZeroDivisionError: raise KeyError
... except BaseException as e:
... e.__context__ = None
... raise
...
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
KeyError
Regards
Antoine.
So, how/why does that work?
Cheers,
Chris
> On Fri, Oct 29, 2010 at 4:02 AM, Antoine Pitrou <soli...@pitrou.net> wrote:
> > On Sun, 24 Oct 2010 10:48:23 +0200
> > "Martin v. Loewis" <mar...@v.loewis.de> wrote:
> >>
> >> You may now wonder whether it is possible to set __context__ to None
> >> somehow. See PEP 3134:
> >>
> >> Open Issue: Suppressing Context
> >>
> >> As written, this PEP makes it impossible to suppress '__context__',
> >> since setting exc.__context__ to None in an 'except' or 'finally'
> >> clause will only result in it being set again when exc is raised.
> >
> > It is not easily discoverable, but it is possible to suppress
> > __context__ by using a bare re-raise afterwards:
> >
> >>>> try:
> > ... try: 1/0
> > ... except ZeroDivisionError: raise KeyError
> > ... except BaseException as e:
> > ... e.__context__ = None
> > ... raise
> > ...
> > Traceback (most recent call last):
> > File "<stdin>", line 3, in <module>
> > KeyError
>
> So, how/why does that work?
A bare "raise" simply re-raises the currently known exception without
changing anything (neither the traceback nor the context).
This __context__ problem is mostly theoretical, anyway. If you want to
present exceptions to users in a different way, you can write a
catch-all except clause at the root of your program and use the
traceback module to do what you want.
Regards
Antoine.
I see. I'd wrap this like this:
def raise_no_context(e):
try:
raise e
except:
e.__context__=None
raise
d = {}
try:
val = d['nosuch']
except KeyError:
raise_no_context(AttributeError("No attribute 'nosuch'"))
The downside of this is that the innermost frame will be
raise_no_context, but I suppose that's ok because even the
next-inner frame already reveals implementation details that
developers have learned to ignore.
Regards,
Martin
I wanted to raise an exception that would be more meaningful to the
caller, but the traceback included info on the original exception,
which is an implementation detail.
I understand that it can be useful, but IMHO there should be a simple
way of suppressing it.
# Exclude the context
try:
command_dict[command]()
except KeyError:
raise CommandError("Unknown command")
# Include the context
try:
command_dict[command]()
except KeyError:
raise with CommandError("Unknown command")
+1
Presumably, this would also keep the context if an actual error occured.
~Ethan~
I agree. It seems to me that the suppression should happen on the raise
line, so that we aren't losing the extra information if an actual error
occurs in the error handler.
~Ethan~
>>I think what's disturbing about this is that the two halves of
>>the extended traceback are printed in the wrong order. We're
> True, but swapping the order would only worsen Steve's problem.
Yes, I can see that what Steve's problem requires is a way
of explicitly saying "replace the current exception" without
attaching any context.
However, in the case where the replacement is accidental,
I think it would make more sense to display them in the
opposite order. Both of the exceptions represent bugs in
that situation, so you will want to address them both, and
you might as well get the traceback in a non-confusing order.
--
Greg
> If you want to present exceptions to users in a different way ...
sys.stderr.write \
(
"Traceback (most recent call last):\n"
...
"AttributeError: blah blah blah ...\n"
)
> Yes, *if the exception is caught* then it doesn't make any difference.
> If the exception creates a traceback, however, I maintain that the
> additional information is confusing to the consumer (while helpful to
> the debugger of the consumed code).
If an exception propagates all the way out to the top level and
all the user gets is a system traceback, the program isn't very
good. Really. If you're concerned about the display format of
Python tracebacks seen by end users, you have bigger problems
with your code.
If it's a server-side program, you need to catch
exceptions and log them somewhere, traceback and all. If it's
a client-side GUI program, letting an exception unwind all the
way out of the program loses whatever the user was doing.
It's usually desirable to at least catch EnviromentError near
the top level of your program. If some external problem causes a
program abort, the user should get an error message, not a traceback.
If it's a program bug, you can let the traceback unwind, displaying
information that should be sent in with a bug report.
John Nagle
> I am the programmer, and when i say to my interpretor "show this
> exception instead of that exception" i expect my interpretor to do
> exactly as i say or risk total annihilation!! I don't want my
> interpreter "interpreting" my intentions and then doing what it
> "thinks" is best for me. I have a wife already, i don't need a virtual
> one!
OK, Ranting Rick, that was funny, and worthy of your name. :^)