Side Track – Global and Local Variables


… once a week there’s an excursion to the local Roman ruins where you can buy cherryade and melted ice cream and bleedin’ Watney’s Red Barrel, and one night they take you to a local restaurant with local colour and colouring

In the previous tutorial, I used a strange and cumbersome approach to storing the data that I needed to keep track of the image files in the directory, the total number of images and what image we were up to.  Instead of just saving the current image number in a variable called currentImage I instead created a dictionary called indexes and a key called currentImage, then assigned the value to that key.  I did the same thing for totalImages as well.  You might, justifiably, be thinking – why didn’t we just use two variables called currentImage and totalImages?  This tutorial is going to try to explain that.

Let's set up the dictionary using code from the previous tutorial:
>>> indexes = {}  # create a dictionary
>>> indexes['totalImages'] = 17 # 'totalImages is a key in the dictionary
>>> # 17 is the perfectly random number (honest, no number is more random!)
... indexes['currentImage'] = 1

Now let’s assign this value to a variable called currentImage:

>>> currentImage = 1

Confirm what we’ve typed:

>>> indexes
{'currentImage': 1, 'totalImages': 17}
 >>> currentImage
 1

So far, so good.  The problem comes when we try to refer to these in a function.  Let’s define a function which tries to print them:

>>> def printIndexes():
 ...   print indexes
 ...
 >>> printIndexes()
 {'currentImage': 1, 'totalImages': 17}

Now, let’s do function which tries to print the value of currentImage:

>>> def printCurrentImage():
 ...     print currentImage
 ...
 >>> printCurrentImage()
 1

So far we haven’t had a problem.  However, in our slideshow application we needed to increment the value of currentImage, so let’s change our functions to do that:

>>> def incrementIndexes():
 ...   indexes['currentImage']+=1
 ...

Confirm the starting values, run the function, then see what the ending values are after calling the function:

 >>> indexes
 {'currentImage': 1, 'totalImages': 17}
 >>> incrementIndexes()
 >>> indexes
 {'currentImage': 2, 'totalImages': 17}

Now, do the same for the variable currentImage:

>>> def incrementCurrentImage():
 ...    currentImage += 1
 ...
 >>> currentImage
 1
 >>> incrementCurrentImage()
 Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "<stdin>", line 2, in incrementCurrentImage
 UnboundLocalError: local variable 'currentImage' referenced before assignment

This worked for the dictionary, but it all blew up when we tried to do the same thing with the variable.  Of note is the error message: local variable ‘currentImage’ referenced before assignment.  This error can be avoided by use of the global keyword – but don’t avoid it this way:

>>> def incrementCurrentImage():
 ...    global currentImage
 ...    currentImage +=1
 ...
 >>> currentImage
 1
 >>> incrementCurrentImage()
 >>> currentImage
 2

The reason that the function failed originally is that Python was looking for a ‘local’ variable.  That is, a variable whose name only makes sense while the program is within the function. Local variables serve an important function in allowing you to modularise your code and re-use the same variable names but with different meanings in different contexts.  Thus, in a function which is processing strings the variable currentValue can be doing something different to a variable with the same name called currentValue but in a different function processing (eg) dictionaries.   This is especially true with recursive functions -but don’t expect to see any recursive functions here!

Unless you expressly tell Python otherwise, if a variable is changed inside a function, it is a local variable (if a variable is only read, then Python is happy to assume it’s a global variable – in a sense, it has to be because if it’s only read, it must have got its value somewhere outside the function).  This means it must be initialised within the function (since its name has no meaning outside the function).  If it hasn’t been initialised Python gets confused.  Hence the UnboundLocalError: it gave us.

How about this:

>>> aVariable = 1
 >>> def incrementLocally():
 ...   aVariable = 1
 ...   aVariable += 1
 ...   print aVariable
 ...
 >>> print aVariable
 1
 >>> incrementLocally()
 2
 >>> print aVariable
 1

In this example, despite doing stuff to aVariable inside the function, the variable called aVariable that we started with has been unchanged.  Now try this:

>>> def incrementLocallyAgain(aVariable):
 ...    aVariable += 1
 ...    print aVariable
 ...
 >>> print aVariable
 1
 >>> incrementLocallyAgain(aVariable)
 2
 >>> print aVariable
 1

The same thing happened, even though we passed in aVariable as an argument to the function.   Within the function aVariable was a local variable.  It got a value when the function was started and that value was assigned locally.  Because local variables must be assigned a value before they are referenced it also follows that each time a function is called, its local variables are reset or reinitialised.  Indeed, each time the function is called, a whole new piece of memory is allocated to the function to run in.  This also means that local variables are not remembered from one invocation of the function to another (and even if they were, the need to initialise the variable would end up forgetting the old value anyway).

So, why did it work for a dictionary?  This is because dictionaries provide a level of indirection to the values stored in them.  The local functions never tried to assign a value to the name used by the dictionary, they only ever tried to assign a value to a key within the dictionary.  That is, they never said

indexes = 5

Rather, they said

indexes['someKey']=5.

So Python only looks up the dictionary.  When it doesn’t find it locally, it assumes it (the dictionary) is global.  From there it can access and change the values of keys in the dictionary.

You can see what values are local and global using two specific functions called locals() and globals() respectively.  These functions return a dictionary with the names and values of local and global variables respectively.  They are also additional examples of Python’s introspection as we discussed in our earlier tute.

>>> def incrementLocally():
...    aVariable = 1
...    aVariable += 1
...    print aVariable
...    print 'locals = ',locals()
...
>>> aVariable = 6
>>> incrementLocally()
2
locals =  {'aVariable': 2}
>>> aVariable
6

Usually you won’t need to worry about whether something is local or global because it will naturally be determined by the way you do your code.  As a rule of thumb you should avoid using ‘global’ variables.  Unless you are very careful about their use, they can make your code very hard to understand and maintain.  If you need to make reference to a variable from within a function, then pass the variable in as a parameter to the function.

About these ads

2 Responses to Side Track – Global and Local Variables

  1. Pingback: Python 4 Kids: Side Track – Global and Local... | Python | Syngu

  2. Pingback: Linux News » Python4Kids New Tutorial: Side Track – Global and Local Variables

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 74 other followers

%d bloggers like this: