(Control): It's important to know at cleanup time whether and how the
user's suite was completed, and if it raised an exception, which one.
(Cleanliness): It's important to hide from the end user the machinery
that's used to implement the transaction or locking protocol.
(Nestability): It must be possible to safely implement nested
transactions.
There are two concrete proposals for new syntax and semantics (apart
from variants about which keyword to use and whether to combine it
with else or except clauses):
(Ty Sarna):
enter object:
suite
calls object.__enter__(), then executes suite, then calls
object.__leave__() with an indication of whether suite terminated with
an exception or not
(Jim Fulton):
enter object:
suite
compiles suite into a code object, and calls object(suite_code, globals,
locals) where globals and locals are the dictionaries containg global
and local variables of the scope containing the enter statement.
(Jim Roskind and I have proposed several ways of doing this without
changes to the language, using destructors and/or try...finally, but
neither of these satisfies the "cleanliness" requirement.)
Now for my response.
My concern with Jim's proposal is that it uses code objects. These
are a mechanism that is part of the language's current implementation.
It is a Bad Thing to use these to describe semantics for the language.
Code objects are currently used in two places: to implement ".pyc"
files, and returned by the compile() function to be passed into exec
or eval(). The former use is clearly purely a matter of
implementation. The latter is fairly esoteric, and is in fact an
ad-hoc measure to enable certain optimizations. As the language
designer, I want to keep code objects out of the specification of the
semantics of my language (though they could be a common extensions).
This is why I much prefer Ty's proposal: it doesn't need code objects.
Jim sees problems with it -- I think he is afraid that it doesn't
satisfy the Control and Nestability requirements. For Control, the
crucial thing is to know how and why the suite was terminated.
Fortunately, this is easily accomplished: just pass the exception
type, value and traceback as parameters to the __leave__ method
(making them None if the suite terminated without raising an exception).
We then get the following semantics:
enter object:
suite
1. Call object.__enter__(). If this raises an exception, execution of
the enter statement is abandoned.
2. Execute the suite.
3. If suite terminates without raising an exception (not counting
exceptions handled within it), call object.__leave__(None, None, None).
4. If the suite terminates with an exception, call
object.__leave__(exc_type, exc_value, exc_traceback).
5. In either case, if the call to object.__leave__(...) terminates
normally, the enter statement terminates normally; if it raises an
exception, it may be handled in the scope containing the enter
statement. (There should be a way to emulate the effect of "finally",
which is not to disturb the stack trace. This is a separate, and
minor issue.)
It is possible that there are race conditions in this proposal that
worry the transaction processing experts (which I'm not). A
refinement of the proposal could be to save the return value of
object.__enter__(), and call *its* __leave__ method. Then the
destructor for this object could check whether its __leave__ had been
called at all or not, and if not, conclude that this was caused by an
exception that occurred in the __enter__ method. Come to think of it
we can almost rewrite the semantics in terms of try...finally:
_tmp = object.__enter__()
try:
suite
finally:
_tmp.__leave__(sys.exc_type, ...)
except that the current system doesn't give sys.exc_type etc. the
right values when the finally clause is entered.
A simplicfication could be to just call object() and _tmp(...) rahter
than __enter__ and __leave__ methods.
Note that this should also make it easier to keep track of nested
transactions on the same object.
Some less important points:
- The choice of a keyword. How about 'in'? ("in object: suite")
- Combining this with except clauses. I'm not for it, if only because
there's a potential confusion: does
enter object:
suite
except:
handler
mean the same as
enter object:
try:
suite
except:
handler
or the same as
try:
enter object:
suite
except:
handler
I could probably defend either way, which means that it will always
confuse a fraction of the users. (For the same reason it's not
allowed to combine except and finally clauses on a try statement.)
--Guido van Rossum, CWI, Amsterdam <mailto:Guido.van.Rossum@cwi.nl>
<http://www.cwi.nl/cwi/people/Guido.van.Rossum.html>