[Python-Dev] anonymous blocks as scope-collapse: detailed proposal

Jim Jewett jimjjewett at gmail.com
Thu Apr 28 23:53:36 CEST 2005


Based on Guido's opinion that caller and callee should both be
marked, I have used keywords 'include' and 'chunk'.  I therefore
call them "Chunks" and "Includers".

Examples are based on

(1)  The common case of a simple resource manager.  e.g.
http://mail.python.org/pipermail/python-dev/2005-April/052751.html

(2)  Robert Brewer's Object Relational Mapper
http://mail.python.org/pipermail/python-dev/2005-April/052924.html
which uses several communicating Chunks in the same Includer, and
benefits from Includer inheritance.

Note that several cooperating Chunks may use the same name
(e.g. old_children) to refer to the same object, even though
that object is never mentioned by the Includer.

It is possible for the same code object to be both a Chunk and
an Includer.  Its own included sub-Chunks also share the top
Includer's namespace.

Chunks and Includers must both be written in pure python,
because C frames cannot be easily manipulated.  They can
of course call or be called (as a unit) by extension modules.

I have assumed that Chunks should not take arguments.  While
arguments are useful ("Which pattern should I match against
on this inclusion?"), the same functionality *can* be had by
binding a known name in the Includer.  When that starts to get
awkward, it is a sign that you should be using separate
namespaces (and callbacks, or value objects).

"self" and "cls" are just random names to a Chunk, though
using them for any but the conventional meaning will be as
foolhardy as it is in a method.

Chunks are limited to statement context, as they do not return
a value.  

Includers must provide a namespace.  Therefore a single inclusion
will turn the entire nearest enclosing namespace into an Includer.
    ?  Should this be limited to nearest enclosing function or
       method?  I can't think of a good use case for including
       directly from class definition or module toplevel, except
       registration.  And even then, a metaclass might be better.

Includers may only be used in a statement context, as the Chunks
must be specified in a following suite.  (It would be possible to
skip the suite if all Chunk names are already bound, but I'm not
sure that is a good habit to encourage -- so initially forbid it.)

Chunks are defined without a (), in analogy to parentless classes.
They are included (called) with a (), so that they can remain first
class objects.

Example Usage
=============

def withfile(filename, mode='r'):
    """Close the file as soon we're done.

    This frees up file handles sooner.  This is particularly important
    under Jython, or if you are using files in cyclic structures."""
    openfile = open(filename, mode)
    try:
        include fileproc()  # keyword 'include' prevents XXX_FAST optimization
    finally:
        openfile.close()

chunk nullreader:           # callee Chunk defined for reuse
    for line in openfile:
        pass

withfile("testr.txt"):      # Is this creation of a new block-starter a problem?
    fileproc=nullreader     # Using an external Chunk object

withfile("testw.txt", "w"):
    chunk fileproc:         # Providing an "inline" Chunk
        openfile.write("Line 1")

#   If callers must be supported in expression context
#fileproc=nullreader
#withfile("tests.txt")      # Resolve Chunk name from caller's default
                            # binding, which in this case defaults back
                            # to the current globals.
                            # Is this just asking for trouble?
                        

            
class ORM(object):

    chunk nullchunk:                # The extra processing is not always needed.
        pass
    begin=pre=post=end=nullchunk    # Default to no extra processing

    def __set__(self, unit, value):
        include self.begin()
        if self.coerce:
            value = self.coerce(unit, value)
        oldvalue = unit._properties[self.key]
        if oldvalue != value:
            include self.pre()
            unit._properties[self.key] = value
            include self.post()
        include self.end()

class TriggerORM(ORM):
    chunk pre:
        include super(self,TriggerORM).pre()    # self was bound by __set__
        old_children = self.children()          # inject new variable
    
    chunk post:
        include super(self,TriggerORM).post()
        for child in self.children():
            if child not in old_children:       # will see pre's binding
                notify_somebody("New child %s" % child)


As Robert Brewer said, 

> The above is quite ugly written with callbacks (due to
> excessive argument passing), and is currently fragile
> when overriding __set__ (due to duplicated code).

How to Implement
----------------

The Includer cannot know which variables a Chunk will use (or
inject), so the namespace must remain a dictionary.  This precludes
use of the XXX_FAST bytecodes.  But as Robert pointed out, avoiding
another frame creation/destruction will compensate somewhat.

Two new bytecodes will be needed to handle the jump and return to
a different bytecode string without setting up or tearing down a
new frame.  Position in the Includer bytecode will need to be kept
in a stack, though it might make sense to use a frame variable
instead of the execution stack.

With those two exceptions, the Includer and Chunk are both
composed entirely of valid statements that can already be
compiled to ordinary bytecode.

-jJ


More information about the Python-Dev mailing list