[Python-Dev] exec/with thunk-handling proposal
holger krekel
pyth@devel.trillke.net
Mon, 3 Feb 2003 14:09:34 +0100
Hello,
I'll try to take the discussion about a "unified approach"
towards 'code block' or thunk handling into a fresh thread
and present what Michael Hudson truthfully calls
"a friendly competing approach".
Preliminaries
-------------
We have some use cases for a construct that allows
encapsulating try/except/finally hooks into
an object. Especially for timely finalization (of files,
locks or other resources) we don't want to scatter
try-finally constructs all over the place.
Please understand that the following proposal has
nothing to do with "function-level" thunk-manipulation
ala classmethod but only with "inline" thunks (which
don't and shouldn't have their own scope).
Syntax and semantic proposal
----------------------------
I think we can may get away with only a "weak" keyword
and allow the aforementioned encapsulation of execution
events into an object like this:
exec expr [with params]: suite
where the expression is evaluated to return a
"thunk" handler with these optional "execution" hooks:
def __enter__(self):
"before suite start"
def __except__(self, type, value, tb):
"swallow given exception, reraise if neccessary"
def __leave__(self):
"""upon suite finish (not called if __except__
exists and an exception happened)
"""
The above "with" parameters (of the form name=expr, comma-separated)
are bound in local (or global/nested) *and* handler instance
namespace. The 'suite' is what we call "thunk".
The above logic allows clean timely finalization for
*multiple* ressources:
exec autoclose() with f1=open(name1), f2=open(name2, 'w'):
for line in f1:
...
f2.write(...)
which would execute as follows
a) autoclose() instance is created and stored as the
"thunk"-handler
b) f1/f2 are stored as attributes on the autoclose instance
c) f1/f2 are put into the local/global namespace (and nested ones
if rebinding is allowed)
d) thunk executes (for line ...)
e) autoclose 'leave' hook is called (with or without exception)
and is implemented like this:
def __leave__(self):
for obj in self.__dict__.values():
obj.close()
f) thunk handler is removed
Because computing 'f1' may succeed but 'f2' can subsequently
fail the assignments *have to* execute within "autoclose"
control.
Now on to the usage of the except hook. Nice use cases might be
exec retry(maxretry=3, on=IOError):
# do network io-stuff
or
exec skip_on(AttributeError, TypeError):
some_object.notify_hook()
but i am sure there are more. Exception handling is often
ugly when inlined with the code. I think that stating
'exception behaviour' up-front allows to write nice
readable constructs.
__exit__ versus __leave__
---------------------------
One remark (mainly to Michael as he does that other
patch) about the hook-name __leave__ versus __exit__.
we may want to eventually allow 'yield' within the
thunk and then '__exit__' would be misleading. Here is
the use case:
exec self.mylock: # would lock/unlock on entering/leaving
# the generator
...
for whatever in something:
yield whatever
...
Or do you think that this (future) use case warrants
yet another hook?
If there is interest i can probably modify my patch
to allow all of the proposed syntax so that you could
play around with it.
the next additional idea is not essential for my so-far
proposal (but hopefully interesting, nonetheless).
regards and interested in comments,
holger
------------- Catching values ala Quixote -------------------
With my former patch there was another "execution" event:
the catching of "dropped" values ala Quixote. For values
that are not assigned to a name and would be discarded otherwise
the execution handler can implement another hook:
def __catch__(self, value):
"catch dropped value from thunk"
this allows anonymous (un-named) objects to be passed
*within the thunk* to the execution handler.
some example use-cases:
exec debug:
if condition:
"something questionable happenend"
so you can quite completly encapsulate debug-handling
into one object. Note that this exec could be
special-cased to do nothing if "debug" is None.
Another one (ala the Quixote-framework):
exec html_stream:
"<h1>title</h1>"
"<p>%s</p>" % html_quote(para)
or even:
exec html.tr():
exec html.td(): "column"
which comes close to what Walter wants. And maybe
you understand now why I choose this strange xml-syntax with
my former patch :-) Actually i didn't have the idea of
reusing 'exec' which makes a lot more sense to me, now.
again with regards and intersted in any remarks,
holger