Creating anonymous functions using eval

Julian Smith jules at REMOVETHIS.op59.net
Mon Aug 22 21:04:31 CEST 2005


On 12 Jul 2005 08:28:45 -0700
"Devan L" <devlai at gmail.com> wrote:

[ here's some context:
> > I've been playing with a function that creates an anonymous function by
> > compiling a string parameter, and it seems to work pretty well:
> > 
> >     def fn( text):
> >         exec 'def foo' + text.strip()
> >         return foo
> > 
> > This can be used like:
> > 
> >     def foo( x):
> >         print x( 2, 5)
> > 
> >     foo( fn( '''
> >         ( x, y):
> >             print 'x^2 + y^2 = ', x*x + y*y
> >             return y
> >         '''))
> > 
> > - which outputs:
> > 
> >     x^2 + y^2 =  29
> >     5
]

> How is this different from a nested function?

It's different because it is defined in an expression, not by a
statement. So you can create a new function inside a function call, for
example. Like I said in the original post, it's a way of overcoming the
limitations of python's lambda.

The simple version I posted fails to lookup things in the caller's context,
which one can easily fix by passing locals() as an extra parameter. Hopefully
it'll be possible to grab the caller's locals() automatically using traceback
or similar.

Here's a version I've been using for a while which seems to work pretty
well:

def fn( text, globals_={}, locals_={}):
    '''
    Returns an anonymous function created by calling exec on <text>.
    <text> should be a function definition, ommiting the initial
    `def <fnname>' (actually, leading `def' can be retained, for
    clarity). Any leading/trailing white space is removed using
    str.strip(). Leading white space on all lines will be handled
    automatically by exec, so you can use an indented python
    triple-quoted string.
    
    In addition, newlines and backslashes are re-escaped inside
    single/double-quoted strings. this enables nested use of fn().
    
    example usage:

        fn(
            """
            def ( target, state):
                if target != 'foo':    return None
                return [], None, 'touch foo'
            """, globals(), locals())
    
    Would be nice to grab our caller's globals() and locals() if
    globals_/locals_ are None, using some sort of introspection. For
    now, you'll have to pass globals() and locals() explicitly.
    '''
    text = text.strip()
    if text.startswith( 'def'): text = text[ 3:].strip()
    if not text.startswith( '('):
        raise Exception( 'fn string must start with `(\'')
    
    def _escape_quotes( text):
        '''
        escape newlines and backslashes that are inside
        single/double-quoted strings. should probably do something about
        backslashes inside triple-quoted strings, but we don't bother
        for now.
        '''
        quote = None
        ret = ''
        for c in text:
            if quote:
                if c=='\n':
                    ret += '\\n'
                elif c=='\\':
                    ret += '\\\\'
                else:
                    if c==quote:
                        quote = None
                    ret += c
            else:
                if c=='\'' or c=='"':
                    quote = c
                ret += c
        return ret


    text = _escape_quotes( text)
    
    #print 'execing:', text
    
    # the exec will put the fn in the locals_ dict. we return it after
    # removing it from this dict.
    exec 'def _yabs_fn_temp' + text in globals_, locals_
    ret = locals_['_yabs_fn_temp']
    del locals_['_yabs_fn_temp']
    return ret


- Julian

-- 
http://www.op59.net/



More information about the Python-list mailing list