[Python-Dev] Re: Extended Function syntax

holger krekel pyth@devel.trillke.net
Sun, 2 Feb 2003 05:37:28 +0100


Guido van Rossum wrote:
> I received this from Glyph.  He brings up some interesting use cases
> for thunks.  I guess it could be used for "on" style event handler
> declarations.  Hmm, you could even craft your own case statement with
> his suggestion:
> 
>   switch(expr):
>     case(val1):
>       block1
>     case(val2):
>       block2
>     default:
>       block3
> 
> This actually makes me worry -- I didn't plan thunks to be the answer
> to all problems.  A new idea that could cause a paradigm landslide is
> not necessarily right.

That's a very interesting point.  Now i wonder if the following
implementation report falls (partly) into this category or goes
otherwise off-road.  Please bear with me if it does.  

Recently, i implemented an experimental "execution protocol" for 
Python-2.2.2.  It centers around "indented code blocks" and
their execution.

First i started out to (try to) put indented blocks into a code 
object and allowing to denote before-hand an object that gets 
and executes it. 

(Regarding syntax I will only mention that much for now:  
i experimentally tried out quite a number from the syntactic 
ideas that were brought up here recently). 

Soon i felt that trying to turn an indented block into a code 
object goes against the grain of the CPython implementation. 

Even worse, I couldn't seriously do much with a code object 
apart from executing (or skipping) it and dealing with exceptions.  
It's especially hard to construct a nice enough namespace/scope 
interaction between a code object and its exec-handler.  
Also 'execing' code often destroys optimization options.  
All not so nice.

So i went on and tried to settle on what i really wanted:

a) to allow an object to hook into certain "execution events"  
   and especially to encapsulate error-handling which today 
   is often "inlined": the to-be-handled code block 
   intermingles with the enclosing try-except construct.  

b) to have easy ways of "exchanging parameters" between
   a code block and its execution handler. 
   Without such interaction the handler and its code block
   would be completly orthogonal to each other (which is 
   puristic but doesn't give you much in practise). 

To satisfy a) i implemented the following execution hooks 
(which all return None):

    class some_execution_handler:

        def __enter__(self):
            """called before my indented block is executed """

        def __except__(self, type, value, traceback):
            """called if an exception occurs during execution"""

        def __leave__(self):
            """called when execution is finished.
    
               this is called if there was no exception AND
               if there was an exception but no __except__ hook
            """

Now how to do the b) "exchanging parameters" part?  After some 
experiments I choose to do it *oneway* resp. only in the direction
from code block to execution handler.  The rationale is that 
the execution handler should only *add* behaviour but not 
modify "foreign" local namespaces.  I think this is a 
crucial point. 

This code-to-handler communication is made explicit by
specifying a series of "name=expr" bindings which 
(as usual) have effect in local or global scope but *additionally* 
are set as attributes on the execution handler instance. 

Thus for timely finalization you now only have to

    1. specify objects to be finalized, e.g.:   f1=open('...')

    2. name a handler that implements __leave__ like this:
    
        def __leave__(self):
            for f in self.__dict__.values():
                f.close()

For 'acquire/release' it's even simpler:

    class autolock(threading.RLock):
        __enter__ = threading.RLock.__acquire__
        __leave__ = threading.RLock.__release__

This worked nicely but soon I also wanted a way
to pass *anonymous* values towards an execution
handler.  I went the Quixotish way and added
this fourth and last hook to the execution handler:

    def __catch__(self, arg):
        """catches a dropped value ala Quixote.

           "dropped" means that an expression's result 
           of the code block would otherwise be discarded.
        """

To summarize, the execution protocol now has three semantic
aspects:

    - execution hooks (enter, leave, except)
    - local name bindings set as instance attributes 
    - drop-catch pattern for anonymous object flow 
      from code block to handler 

Now let's assume that i realized this with a XML-like syntax 
(which i actually did but don't see as central), then
the above requirements roughly map to

   '<' expr (NAME '=' expr)* '>' suite

The evaluated 'expr' is the execution handler and 'suite'
is the code block.  It's hopefully evident now how
the following example usages work:

    <autoclose() f1=open('/etc/passwd')>
        data = f1.read()
    # here f1 will be closed no matter what

    def some_class_method(self, ...):
        ...
        <self.lock1>
            # in critical part here


    <debug> u"hello world"   # debug object catches dropped object

    <async_remote>          # handles the remote call including errors
        remote.call(...)    

    <shell> "ls -la"        
    if shell.errno:
        ...

I could imagine that you find this mixture of XML syntax
and python horrible.  FWIW i found that this "markup" in
a way resembles the notion of 'adding characteristics and meaning' 
to a code block much like 'html' tags do for text.  

However, it's certainly better to focus on semantic ideas 
and problems first before discussing syntactic details.  

In the hope of not just having wasted your time,

    holger