Securing a future for anonymous functions in Python

Steven Bethard steven.bethard at gmail.com
Fri Dec 31 19:19:59 CET 2004


Alex Martelli wrote:
> Paul L. Du Bois <polytope at gmail.com> wrote:
>>def fn(gen):
>>    """Turns a generator expression into a callable."""
>>    def anonymous(*args): return gen.next()
>>    return anonymous
>>
>>def args():
>>    """Works with fn(); yields args passed to anonymous()."""
>>    while True: yield sys._getframe(2).f_locals['args']
>>
>>args = args()
>>
>>foo = fn(a + b * c for (a,b,c) in args)
>>assert foo(3,4,5) == 3+4*5
>>assert foo(4,5,6) == 4+5*6
> 
> 
> Paul, you really SHOULD have posted this BEFORE I had to send in the
> files for the 2nd ed's Coobook... this gets my vote for the most
> delightful abuse of sys._getframe even (and I've seen quite a few;-).

So, I couldn't figure out why this worked until I started to write an 
email to ask.  Once I understood it, I figured it wouldn't hurt to send 
my thoughts out anyway to (1) verify that I understand it right, and (2) 
help anyone else who was trying to figure this out.


As I understand it sys._getframe(2).f_locals should get the names local 
to the stack frame two above the current one.  So, in the context of:
     fn(... for ... in args)
sys._getframe(2).f_locals should be looking at the names local to the 
'anonymous' function in the 'fn' function, because one stack frame up 
from the 'args' function is the generator's 'next' function, and two 
stack frames up is the 'anonymous' function.  That means that:
     sys._getframe(2).f_locals['args']
gets whatever object has been bound to 'args' in:
     def anonymous(*args):
So then in:
     foo = fn(a + b * c for (a,b,c) in args)
     foo(3,4,5)
     foo(4,5,6)
sys._getframe(2).f_locals['args'] will get (3, 4, 5) in the first foo 
call, (4, 5, 6) in the second foo call, etc.


So basically the way a call like foo(3, 4, 5) works is:
(1) foo(3, 4, 5) calls gen.next() where gen is the generator expression
(2) gen.next() calls args.next()
(3) args.next() returns the (3, 4, 5) argument tuple of foo by looking 
up the stack frames
(4) gen.next() binds (3, 4, 5) to the names a, b, c respectively
(5) gen.next() returns the value of "a + b * c" for these bindings
(6) foo(3, 4, 5) returns the same value (as gen.next() did)

Does that seem about right?

Steve

P.S.  That's so *evilly* cool!



More information about the Python-list mailing list