[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