next up previous contents
Next: Function Basics Up: Functions Previous: Introduction   Contents

Scoping: How Python finds your variables

When you write a program in Python, you can generally refer to variables anywhere in the program and Python will find the appropriate values. But when you create a function, it could potentially be dangerous for Python to treat the variables within a function as equivalent to the ones which were used outside the function. Consider this simple example of a function, which adds all the values in a list:
       def addem(list):
           sum = 0.
           for i in list:
               sum = sum + i
           return sum
If Python didn't provide some mechanism for ``private'' variables within functions, every time you called the addem function, the variable i would get overwritten by the last value in the list being processed by the function. When you define a function, Python creates a separate namespace within that function A namespace is a mapping between object names in your program and the location in memory where Python actually stores their values. So when you create a variable inside a function, Python understands that you mean the local variable in the function's name space, and not a variable of the same name defined somewhere else. Thus, in the addem function, the variables i and sum would always be resolved as variables local to the function, not as variables which also exist somewhere else in your program.

To make your programming life easier, if you refer to a variable inside a function which already exists in your program at the time the function was called, Python will understand what you are talking about, even though that variable is not in the functions' local namespace. Consider this program:

scale = 10.

def doscale(list):
    newlist = []
    for i in list:
        newlist.append(i / scale)
    return newlist

mylist = [1,2,3,4,5]
otherlist = doscale(mylist)
print otherlist                       # result is [0.1, 0.2, 0.3, 0.4, 0.5]
Since scale was set to the value of 10. before the function was called, when you refer to scale inside of the function, it finds its value correctly. If you had set scale to a value inside the function, on the other hand, Python would have created a new variable called scale within the function's namespace and used that value, ignoring the original value of the variable. Furthermore, if scale was not defined inside the function, any attempt to change scale's value inside the function would result in an error.

You can override this behavior by using the global statement. When a variable is declared as being global inside a function, Python will always look in the global namespace, i.e. the namespace of already defined objects, to find the value of the variable; if you change the variable inside the function, the value of the original variable is changed when the function is called. In general, you should only use global variables when there's no other (reasonable) way to solve a problem. If you find that you need to rely on global variables to get a job done, or that you're refering to lots of variables in your function which were not passed as arguments you should consider the object-oriented programming techniques introduced in Chapter 10.

As an example of a global variable, suppose we wish to count how many times a function is called inside of a program. If we try to keep track of this information with an ordinary variable inside a function, we won't be able to remember the variable's value between function calls. Thus, a global variable, initialized to 0 at the beginning of the program, is a logical way to solve the problem.

count = 0

def doit(x):
        global count
        count = count + 1
        return x + 1

tst = [10,19,25,18,17,23,29]

for i in range(len(tst)):
        if tst[i] % 2 == 1:
                tst[i] = doit(tst[i])

print 'doit was called %d times.' % count   
If this program were run, it would report that the function was called 5 times, since there are 5 odd numbers in the vector tst. If the global statement were not included, the program would have failed with a NameError exception, since count had not been set to a value inside a function.

Thus, when Python is trying to figure out what a name in your program refers to, names in a functions' local namespace take priority over all others, followed by names of global objects, or objects which were imported into the global namespace from a module (See Section 1.4.4). Finally, the names of the builtin objects are searched to resolve the issue. Because of this order, Python's scoping is sometimes said to follow the LGB rule.

One important exception to this rule, introduced in Python 2.2, involves functions which are defined within another function. Consider this simple function, designed to append a symbol to the beginning of every string in a list:

def addsym(sym,strings):
   def doadd(strs):
       new = []
       for i in strs:
           new.append(sym + i)
       return new
   return doadd(strings)
When Python tries to resolve the name sym inside the function environment created by the definition of doadd using only the LGB rule, it will not be able to find a suitable value. If the definition of doadd were not in a function, Python could find the value of sym in the global namespace; it's the nesting of the function definition inside of another function which creates the difficulty. Thus, in nested environments such as this, if the LGB rule does not resolve a particular name, the local namespace of the function in which the lambda expression was defined is examined to resolve the name.

This use of nested scopes makes it easy to construct closures in Python. A closure is basically a function which will always be evaluated using the namespace in which is was created, regardless of the evironment from which it was called. As a simple example, suppose we wanted to append a number to the end of a string. We could write a function to generate a function to append a particular number on a string as follows:

def genfun(num):
    def f(str):
        return str + `num`
    return f
Notice that genfun returns a function as its value; each time we call the function, it creates a new function to append a particular number to the end of a string. For example,
>>> one = genfun(1)
>>> two = genfun(2)
>>> three = genfun(3)
>>> str = 'name'
>>> print one(str),two(str),three(str)
name1 name2 name3
Regardless of the value of num in the calling environment, the functions produced by genfun will append the value of num which was passed to getnum when it was called to produce the function.

next up previous contents
Next: Function Basics Up: Functions Previous: Introduction   Contents
Phil Spector 2003-11-12