[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