[Python-Dev] Explicit Lexical Scoping (pre-PEP?)

Phillip J. Eby pje at telecommunity.com
Fri Jul 7 19:25:43 CEST 2006


At 09:56 PM 7/6/2006 -0400, Kevin Jacobs <jacobs at bioinformed.com> wrote:
>Why not extend the interface to the locals builtin and add a __getitem__ 
>that returns a proxy to access locals defined in other lexical scopes via 
>__{get/set/del}attr_:
>
>def counter(num):
>     num = 1
>     def inc():
>         locals[1].num += 1
>         return outer.num
>     return inc
>
>
>Where, for CPython, locals[n] gives access to 
>NamespaceProxy(sys._getframe(n).f_locals).

That doesn't actually work, because sys._getframe(1) will give you inc()'s 
caller, *not* the frame where it was defined.


>   In addition to having a relatively pleasing and explicit syntax, this 
> may be a feasible method for allowing portable introspection into outer 
> scopes without having to export the whole frame object a la 
> sys._getframe(n).  I strongly suspect that Jython, IronPython, and PyPy 
> would have little difficulty supporting (and optimizing) this construct.
>
>Lacking core language support, it is easy to roll an object that does just 
>what I suggest.  Actual implementation is left to a more motivated reader, 
>of course.

While I hesitate to describe anything as "impossible", I will note that an 
implementation with the syntax you suggest is not likely to be possible 
without resorting to ctypes or a C module, because the frame doesn't have a 
reference to the function, only the code object, and it's the function 
objects that own the closure cells.

I think the only way you can reasonably accomplish rebinding in Python 
right now is to have a rebind(func, var=value) function, e.g.:

     def counter(num):
         def inc():
             rebind(inc, num=num+1)
             return num
         return inc

It doesn't allow augmented assignment, of course, and it also creates a 
circular reference between 'inc' and itself, requiring garbage collection 
to clean up.

Anyway, the actual implementation of such a rebind function would basically 
be a bit of syntax sugar over this cookbook recipe:

http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440515

You'd want to just use the keyword argument names to figure out which cells 
of the function needed changing.  And the whole thing would be really slow 
and complex.  You'd probably be better off using function attributes:

     def counter(num):
         def inc():
             inc.num+=1
             return inc.num
         inc.num = num
         return inc

It's not ideal, but *this* should probably be the pattern we recommend for 
rebinding, rather than "create a class" or "use an anonymous namespace 
argument".

It can even be prettied up a little bit with a decorator to set the 
function attributes.

     def counter(num):
         @uses(num=num)
         def inc():
             inc.num+=1
             return inc.num
         return inc

But that's about as good as it gets, which is nowhere near as good as:

     def counter(num):
         def inc():
             nonlocal num
             num+=1
             return num
         return inc

On the other hand, the rebind() syntax actually is slightly cleaner in some 
respects:

     def counter(num):
         def inc():
             rebind(num = num+1)
             return num
         return inc

Maybe we should just make rebind() a fast builtin.  ;)



More information about the Python-Dev mailing list