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