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.