Re: try clauses

Mark S. Riggle (sasmsr@unx.sas.com)
Fri, 1 Jul 1994 11:52:41 -0400

(Guido) writes ->
|> > (Mark) writes ->
|> > The stack in the call to bar on try statement at point of raising my_exec2
|> > will look something like:
|> >
|> > @finally - contains raising my_exec <- current stack position
|> > entering bar
|> > @my_exec - inner one
|> > @my_exec2
|> > @my_exec - outer one
|> > entering foo
|> > #bottom - top level
|> >
|> > and we raise the my_exec2 exception in the 'try' part in bar.
|> > Scan back and find the @my_exec2 marker in the stack. Now
|> > either intervening exception markers are allowed to stay active or
|> > they are not; in this case the inner @my_exec is at issue.
|> > Now we need to do any finally clauses (top down) on the stack
|> > that lie between the top (current position) and the called exception
|> > marker, @my_exec2. The @finally clause raises the my_exec exception.
|>
|> Ah, there's your lack of understanding of how Python exceptions work.
|> There is no @my_exec2 marker on the stack. There is only a marker
|> specifying that a try...except clause is present. Its except clause
|> headers (the names of the exceptions) are evaluated only when an
|> exception reaches the marker -- otherwise, try...except would be much
|> more expensive (Python is a dynamic language, so the exception
|> identities would have to be evaluated each time a try clause was
|> entered -- currently all that's needed is to push a blank marker).

This is an interesting interpreter optimization. It allows unusual
exception catchs where the exception header can be changed in the body
of the try clause. For example:

exec1= 'exec1'
exec2= 'exec2'
dynamicExec= exec1

def foo(x):
try:
print "dynamicExec = ", dynamicExec
bar(x)
except dynamicExec, val:
print "caught execpt ", dynamicExec, " with ", val

def bar(x):
global dynamicExec
dynamicExec = exec2
raise exec2, x

>>> foo(2)
dynamicExec = exec1
caught execpt exec2 with 2
>>>

I do not know if this is a desirable feature or not. It is however an
artifact of the current implementation chosen for the good reason of
efficiency. Other methods of compiling may reduce the cost of
evaluating the headers at entry to the TRY clause and so perhaps the
question of its being a desirable feature or not should be raised. I
again contend that this behavior is not documented (except for here)
and deserves to be.

|>
|> In your model, first the except clause corresponding to the exception
|> is selected, then the finally clauses are executed. However this is
|> not the way I want it, nor can it be done this way. Selecting the
|> except clause is done in the context of the block containing the
|> except clauses; the finally clauses are executed before the except
|> clause is selected, "on the way out" so to speak. Changing the order
|> would mean that control flow would have to go UP the stack (from the
|> except clause back to the finally clause). This is impossible in
|> Python's execution model: once you have left a block you cannot
|> re-enter it. (The reason is that there's a C stack frame
|> corresponding to the Python stack frame -- this is essential for
|> Python's extensibility, otherwise C could could not call back on
|> Python code. Once the C frame has been left it is destroyed.)
|> Placing markers on the stack to identify the various exceptions being
|> caught would complicate the stack data structure and increase
|> execution overhead. It would also be incompatible with the exception
|> handling as it is currently exposed to C extensions.
|>
|> Concluding, finally clauses are not supposed to raise exceptions -- if
|> they do, it's your problem.

I'm confused by the concluding remark. The whole point of this thread
is to determine the current and desired behavior of the language when
exceptions are raised in a finally clause.

What you have done here is explain why the current implementation has its
current behavior. Choices were made for efficiency and for interfacing to C.

However, I do believe that the other model for exception handling (I
do not want to call it mine, how about 'limited extent exceptions')
can be fitted into Python's execution model. It does not need to be
implemented in the method I described. An alternative would be to
make the exception state be a list. So an exception during a
'finally' clause would be added to the end of the list. It is now the
exeception to be handled, but as you go "on the way out" you must
first pass handlers for the prior exceptions. The handlers are not
given control, but they must first be passed before a handler for the
current execption will be accepted. This seems compatible with the
current methods and would still allow the 'dynamic exception
targeting' mentioned above.

|>
|> The reference manual clearly explains the current exception model. I
|> don't think additional documentation is needed.
|>

Let me quote the manual on the relevant part:

"If the 'finally' clause raises another exception or executes a
'return', 'break' or 'continue' statement, the saved exception is
lost."

Well the saved exception is lost in either model of exception
handling. The models differ only on the dynamic extent of the
exception headers. If there are additional parts in the manual that
would define this behavior, I have totally missed them, and I have
RTFM.

|>
|> Please let's put this subject to rest.

An aside note:

There is a recent thread WIGIHBAB (What If Guido Is Hit By A Bus?)
which talks about standards. This is exactly the kind of issue that
standards are about. The current defacto language standard for Python
is the current implementation. This is not necessarily bad, but it is
very important to know why certain artifacts of the language are
there. Was it part of the orginal design of the language semantics or
dictated by the tradeoffs of the implementation. It appears to me
from Guido's comments that both the 'unlimited extent exceptions' and
the 'dynamic exception targeting' are from the tradeoffs of the
implementation. This is not necessarily bad either, but they will
become the grey areas of a language if not turned into black and
white, ie. documented. I do not need to remind you that there are
many ways to implement a language that will differ in areas such as
these two and the implementors would say they implemented the same
language.