Destroying the namespace, or How I learned to love the stack frame

Filed under: python breve 

My work on getting macros to work in Breve forced me to figure something out that's going to drastically improve Breve.

The fundamental problem is that when Python calls a function, that function gets a local namespace. This is usually a good thing, but it's not always what you'd want in a specialized environment such as a template engine. For example, Breve has an include ( template ) feature that lets you include fragments of Breve code into another template. This worked okay, except that there were all sorts of limitations on what you could or couldn't do in an include file. Further, it required building a dictionary to use as the included file's namespace to make sure that user-supplied variables would be available to it.

Enter sys._getframe. This little-utilized function accepts an integer and retrieves the frame that many levels up the execution stack. For example sys._getframe ( 1 ) returns your current frame of execution. This can be used, for example, to retrieve the name of the function you're currently in (useful for debuggers and the like).

What's interesting to me, however, is that sys._getframe ( 2 ) returns the frame of the function that called the current function. This allows you to retrieve the global namespace of that function and put things back into it. In short, you can make the current function change or create variables in the outer scope:

import sys

def caller ( ):
    return sys._getframe ( 2 )

def f ( ):
    g ( )
    print v

def g ( ):
    frame = caller ( )
    print frame.f_code.co_name
    frame.f_globals [ 'v' ] = 2

f ( )

Running this code will output:

f
2

What this means to Breve is that now when a template calls include(), now the included file will be executed in the caller's namespace. It's actually even simpler than what's outlined above because Breve templates are passed to eval, so all that I need to do is pass caller().f_globals as the globals parameter to eval and the magic is done:

co = compile ( 'fragment.b', 'fragment.b', 'eval' )
return eval ( co, caller().f_globals, { } )

This will provide a much smoother interface. I also expect to apply this to template inheritance.

You can find out more about this topic here.



0 comments Leave a comment