except Exception as err, tb [was: with_traceback]

"Jim Jewett" <jimjjewett@gmail.com> wrote:
Guido van Rossum wrote:
Since this can conceivably be going on in parallel in multiple threads, we really don't ever want to be sharing whatever object contains the head of the chain of tracebacks since it mutates at every frame bubble-up.
So (full) exceptions can't be unitary objects.
In theory, raising an already-instantiated instance could indicate "no traceback", which could make pre-cooked exceptions even lighter.
Grrk. I think that this is right, but the wrong way to think of it! If we regard a kind of exception as a class, and an actual occurrence as an instance, things become a lot cleaner. The class is very simple, because all it says is WHAT happened - let's say divide by zero, or an attempt to finagle an object of class chameleon. The instance contains all of the information about the details, such as the exact operation, the values and the context (including the traceback). It CAN'T be an object, because it is not 'assignable' (i.e. a value) - it is inherently bound to its context. You can turn it into an object by copying its context into an assignable form, but the actual instance is not assignable. This becomes VERY clear when you try to implement advanced exception handling - rare nowadays - including the ability to trap exceptions, fix up the failure and continue (especially in a threaded environment). This makes no sense whatsoever in another context, and it becomes clear that the action of turning an instance into an object disables the ability to fix up the exception and continue. You can still raise a Python-style exception (i.e. abort up to the closest handler), but you can't resume transparently. I have implemented such a system, IBM CEL was one, and VMS had/has one. I don't know of any in the Unix or Microsoft environments, but there may be a few in specialised areas. Harking back to your point, your "already-instantiated instance" is actually an object derived directly from the exception class, and everything becomes clear. Because it is an object, any context it includes was a snapshot and is no longer valid. In your case, you would want it to have "context: unknown". Regards, Nick Maclaren, University of Cambridge Computing Service, New Museums Site, Pembroke Street, Cambridge CB2 3QH, England. Email: nmm1@cam.ac.uk Tel.: +44 1223 334761 Fax: +44 1223 334679

Nick Maclaren wrote:
The instance contains all of the information about the details, such as the exact operation, the values and the context (including the traceback). It CAN'T be an object, because it is not 'assignable' (i.e. a value) - it is inherently bound to its context. You can turn it into an object by copying its context into an assignable form, but the actual instance is not assignable.
This has given me another idea: Instead of instantiating the exception when it's raised, what about instantiating it when it's *caught* instead? Suppose for a moment that we take what might seem to be a retrograde step, and go back to the old way of raising exceptions, where you supply the type and arguments separately: raise MyException, ("Bogus value:", x) Now instead of immediately "normalising" this by creating the exception, we keep all three parts (type, args, traceback) separate while we search for a handler. If we find one of the form except MyException as e: then at that point we instantiate the exception, attach the traceback and assign it to e. However, if we find one of the form except MyException: then we don't need to instantiate it at all! This would address the issue of efficiently using exceptions for flow control, since for that kind of use you're probably not going to want the exception object, in which case your except clauses will be of the latter form. Now, I'm not proposing that the raise statement should actually have the above syntax -- that really would be a step backwards. Instead it would be required to have one of the following forms: raise ExceptionClass or raise ExceptionClass(args) plus optional 'with traceback' clauses in both cases. However, the apparent instantiation call wouldn't be made -- it's just part of the syntax. -- Greg

On Sat, Mar 03, 2007 at 11:00:53AM +1300, Greg Ewing wrote:
Now, I'm not proposing that the raise statement should actually have the above syntax -- that really would be a step backwards. Instead it would be required to have one of the following forms:
raise ExceptionClass
or
raise ExceptionClass(args)
Eep, that's awkward. If you are using exceptions for flow control, why would you use the second form? Why not just allow both exception classes and exception instances to be raised, and only instantiate-at-catch in the case of a raise of a class and a catch with an "as" clause? Then the auto-instantiation becomes a "convenience" feature of catch, safely relegating it to an easily-understood and easily-ignored corner of the user's conceptualization of exception handling. The above also looks a lot like the current syntax, but (unless I'm mistaken) ExceptionClass will be instantiated immediately right now. It seems best not to change the semantics of existing syntax if not necessary. I've been snoozing though this conversation until now, so if I've spoken out of turn, please forgive me. Dustin

dustin@v.igoro.us wrote:
Why not just allow both exception classes and exception instances to be raised, and only instantiate-at-catch in the case of a raise of a class and a catch with an "as" clause?
Because that doesn't solve the problem of pre-instantiated exceptions. What I'm proposing is a way of ensuring that a new instance is always created for each raise, so that a traceback can safely be attached to it -- but only if it's going to be used.
The above also looks a lot like the current syntax, but (unless I'm mistaken) ExceptionClass will be instantiated immediately right now.
I think the number of situations where it would make any difference in existing code would be very small. You'd have to go to considerable lengths to detect whether the instantiation was being done at the time of raising or the time of catching. The main difference would be that use of preinstantiated exceptions would become illegal. But this is a 3.0 suggestion, so we can cope with that if we think it's a good enough idea.
It seems best not to change the semantics of existing syntax if not necessary.
A different syntax could be used, but it would be a big upheaval to ask everyone to un-learn the current way of raising exceptions and go back to something that we thought we had left behind as embarrassingly old-fashioned. And there wouldn't really be any option of supporting both ways, because if you can still raise a preinstantiated exception, it wouldn't be safe to attach a traceback to it, meaning we'd have to support two different ways of handling tracebacks as well. It could all get terribly messy. -- Greg

On 3/2/07, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
This has given me another idea: ... Now, I'm not proposing that the raise statement should actually have the above syntax -- that really would be a step backwards. Instead it would be required to have one of the following forms:
raise ExceptionClass
or
raise ExceptionClass(args)
plus optional 'with traceback' clauses in both cases. However, the apparent instantiation call wouldn't be made -- it's just part of the syntax.
Elsewhere here I listed several examples of existing code which raises an instance which was caught or created earlier. That would not be supported if the raise had to be written in your given forms. Andrew dalke@dalkescientific.com

On 3/2/07, Andrew Dalke <dalke@dalkescientific.com> wrote:
On 3/2/07, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
This has given me another idea: ... Now, I'm not proposing that the raise statement should actually have the above syntax -- that really would be a step backwards. Instead it would be required to have one of the following forms:
raise ExceptionClass
or
raise ExceptionClass(args)
plus optional 'with traceback' clauses in both cases. However, the apparent instantiation call wouldn't be made -- it's just part of the syntax.
Elsewhere here I listed several examples of existing code which raises an instance which was caught or created earlier. That would not be supported if the raise had to be written in your given forms.
Raising an instance that was just caught or created a bit earlier should definitely be supported. Especially this pattern is useful, in all its variations: def handle_error(self, exc): """Subclass can override to ignore it.""" raise exc def foo(self): if self.bar(): self.handle_error(RuntimeError("can't foo because of bar")) # if it returns, ignore bar # do foo's thing The question is, how to distinguish this from an exception created *much* earlier and raised repeatedly? Adding an attribute pointing to the "owning thread" might help catch concurrent use, but that wouldn't help if the pattern is used in a single-threaded environment, even though it could still be an error there (e.g. in used in a recursive context). -- --Guido van Rossum (home page: http://www.python.org/~guido/)
participants (5)
-
Andrew Dalke
-
dustin@v.igoro.us
-
Greg Ewing
-
Guido van Rossum
-
Nick Maclaren