Python's scope rules
Alex Martelli
aleaxit at yahoo.com
Thu Dec 21 07:07:04 EST 2000
"Marcin 'Qrczak' Kowalczyk" <qrczak at knm.org.pl> wrote in message
news:slrn943bf3.q3c.qrczak at qrnik.zagroda...
> Wed, 20 Dec 2000 12:47:09 -0500, Terry Reedy <tjreedy at udel.edu> pisze:
>
> > The only things I find 'odd' (and definitely confusing at first)
> > is the use of 'global' for the module namespace (and I suppose
> > there is some history here) and the sometimes claim that there are
> > two rather than three possible scopes.
>
> The fact that scopes don't nest is a practical problem. Yesterday I was
> doing some customizations to viewcvs and wanted to factor out a common
> pattern of code of 5 lines which was repeated 5 times by moving it to
> a local function, but it referred to so many free local variables that
> I finally did not do it, because I would have to pass them explicitly
> and the result would not be much less ugly than the current state.
When I needed to do a similar refactoring I came up with a simple
solution based on 'explicit is better than implicit'.
Original code, say (just typing it in more or less at random!-):
def original(anargument, another, yetanother, onemore):
alocal=whatever(anargument,onemore)
if onemore>alocal:
somethingelse=acleverfun
else:
somethingelse=asimplerfun
for i, j in zip(another,yetanother):
if i>alocal or j<anargument:
onemore.peep(j,i)
else:
somethingelse(onemore,j,i)
another.reverse()
for i, j in zip(another,yetanother):
if i>alocal or j<anargument:
onemore.peep(j,i)
else:
somethingelse(onemore,j,i)
another.reverse()
yetanother.reverse()
for i, j in zip(another,yetanother):
if i>alocal or j<anargument:
onemore.peep(j,i)
else:
somethingelse(onemore,j,i)
another.reverse()
for i, j in zip(another,yetanother):
if i>alocal or j<anargument:
onemore.peep(j,i)
else:
somethingelse(onemore,j,i)
How do I factor out the loop into a local function without
having to pass it half a dozen argument, is the issue.
A simple and reasonably explicit answer might then be:
def factored(anargument, another, yetanother, onemore):
def local_loop():
for i, j in zip(another,yetanother):
if i>alocal or j<anargument:
onemore.peep(j,i)
else:
somethingelse(onemore,j,i)
def runit(func, dict):
exec func.func_code in dict
alocal=whatever(anargument,onemore)
if onemore>alocal:
somethingelse=acleverfun
else:
somethingelse=asimplerfun
dict = locals()
runit(local_loop, dict)
another.reverse()
runit(local_loop, dict)
another.reverse()
yetanother.reverse()
runit(local_loop, dict)
another.reverse()
runit(local_loop, dict)
This kind of approach only works for a "piece of code
to be factored out" that doesn't rebind local variables,
of course; local variables can only be rebound by
assignment statements in local scope. For a more
general case, I think it might be best to introduce
an instance, holding, as its attributes, the relevant
'variables', finessing scope issues entirely through
more explicitness.
The issue of rebinding non-local variables may well
persist even when local scopes are allowed to nest,
depending on difficult design choices regarding how
one differentiates between (re-)binding a variable in
an 'outer' scope, and causing a variable thus named
to 'spring into existence' in _this_ (local) scope.
http://python.sourceforge.net/peps/pep-0227.html
seems to be strongly oriented to simply disallowing
rebinding of variables in outer nested scopes, and
it seems that's what we'll see in Python 2.1. If so,
then factoring-out will still need the introduction
of a state-holding instance (or dirtier 'container'
tricks as PEP 227 suggests) when the factored-out
code needs to rebind (what used to be) locals; but
things will be simpler when no such rebinding occurs.
> I don't want dynamic scoping like in old Lisp and probably nobody wants
> it. But nested lexical scoping is used in other good languages, it's
> so natural and convenient that it's odd that Python does not use it.
> IMHO Python would be a better language with true lexical scoping.
I think you can say 'will' rather than 'would', as I see no
reason why PEP 227 should not make it into Python 2.1.
I do hope that Python keeps the simplicity of avoiding the
distinction between 'let' and 'set!' (creation of a new
binding in this scope, vs modification of existing binding
in some nested scope), variable-declarations, or other
complexities to allow fancy stuff such as rebinding of
lexically-nested-scoped variables. I know of no language
that allows that without constraints -- say that your
scope is currently nested 5 levels deep and there's a
binding for 'x' in each nested scope, how do you specify
'the x that's bound exactly 3 levels up from this one'?
Java specifically disallows homonym variables in nested
scopes, and maybe that should be considered as a part of
PEP 227 -- such homonimy frequently causes confusion in
languages which allow it, I think, and apparently Java's
designers agree.
Alex
More information about the Python-list
mailing list