[Tutor] New Python scoping

Danny Yoo dyoo@hkn.eecs.berkeley.edu
Tue, 6 Nov 2001 15:06:55 -0800 (PST)


On Tue, 6 Nov 2001, dman wrote:

> Have you programmed in C, C++ or Java at all?  If so, then Python's
> scoping is the same except that you can define a function inside
> another, and instances of inner classes don't automagically have
> unqualified access to outer-class members (a java hack to work around
> the lack of references to functions).

The scoping rules probably wouldn't make too much sense if we try
explaining it in C or C++ terms, since both languages don't allow
functions to be nested... Hmmm...

Let's look at a concrete example that uses Python's revised scoping rules,
and it might make things clearer.  Below is something called the pocket()
function --- it's totally useless, but sorta fun to play with.

[Note: for people who played around with the Lists and Lists interactive
fiction game at:

    http://www.wurb.com/if/game/128

the snipped below is a translation of the solution to the last puzzle, so
please skip this message if you don't want to see spoilers.  Also, this is
something of a brain twister anyway, so if it looks weird, that's because
it IS.  *grin*]

### Spoiler space ahead ###







Here goes!  As a warning, this function will look very silly, but please
be patient; we'll play around with it and see what it actually does in a
moment:

###
from __future__ import nested_scopes         ## Necessary in Python 2.1

def makePocket(n):
    """A function that creates a "pocket" function that holds some 'n'
       within it."""
    def pocket_function(*x):
        """Given no arguments, returns the contents of the pocket.
        Given a single argument, creates a new pocket with that argument
        as the new contents."""
        if len(x) == 0:
            return n
        return makePocket(x[0])
    return pocket_function
###

This "pocket" allows us to hold a single thing in a function, like this:

###
>>> mypocket = makePocket("rocket")
>>> mypocket()
'rocket'
###

It's a rocket in a pocket.  *grin* This pocket function is sorta neat
because we can put new things in the pocket:

###
>>> mypocket = mypocket("escaflowne")
>>> mypocket()
'escaflowne'
###

So this "pocket" is a function that, when we call it, returns the very
last thing that we put in it.  When we call the "pocket" with a new thing,
it gives us a new pocket.



How in the world does this thing work?  We can take a look at the function
definition again:

###
def makePocket(n):
    def pocket_function(*x):
        if len(x) == 0:
            return n
        return makePocket(x[0])
    return pocket_function
###

makePocket() is a function that knows how to make new pockets().  When we
make a new pocket, we need to build a pocket_function that knows how to
return the thing that the pocket contain, and that's what the the inner
definition does.  Once makePocket() has built a pocket_function(), it
gives that back to the user to fiddle with.


All of the scoping stuff has to do with pocket_function() --- how does
pocket_function() know what 'n' is?  In the old Python scoping rules, it
couldn't!  The only places ("scopes") where functions could look for
variables were:

    1.  Local --- within our own function
    2.  Module --- within our own module
    3.  Builtin --- within the built-in namespace


If we take a close look at the pocket_function():

###
    def pocket_function(*x):
        if len(x) == 0:
            return n
        return makePocket(x[0])
###

we can imagine what older versions of Python thought when it saw the
variable 'n'.  "Is 'n' something we defined in pocket_function?  No.  Is
it something that was defined at the module level, like a global variable?  
Nope.  How about in the builtin level?  No.  Aaaah, I don't know what n
is!"

###
## In Python 1.52:
>>> pocket = makePocket("rocket")
>>> pocket()
Traceback (innermost last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 4, in pocket_function
NameError: n
###



What makes Python's modified scoping rules neat is that it doesn't
restrict the looking up of variables to just three places --- instead,
scopes can be "nested", or built on top of each other.  In the new
"nested" scoping rules, the pocket_function() in:

###
def makePocket(n):
    def pocket_function(*x):
        if len(x) == 0:
            return n
        return makePocket(x[0])
    return pocket_function
###

has more flexibility when it looks up variables.  First, pocket_function()  
can look things up in it's own, "local" scope.  Next, it can go one level
outside to the scope of the makePocket() function, and so it can find out
what 'n' is.

Please feel free to ask questions about this; it's a very strange thing to
see if you're coming from a strict C/C++ background, so don't worry if it
looks weird.