20130131

This week we looked at several topics:

Global and Local Variables

Dictionaries

Recursion


Global and Local Variables

A Global Variable is a variable that is assigned a value in the main program.   A Local Variable is a variable that is assigned a value inside a function - this includes any parameters that the function might have.  Local variables can only be used and referred to inside the function where they are defined.

Global and local variables can have the same name.  Consider this code:

def fun_1():
    x = 3
    print "in fun_1 x = ",x
    fun_2()
    print "back in fun_1 x = ",x
   
def fun_2():
    print "in fun_2 x = ",x
   
x = 4   
fun_1()
print "in main program, x = ",x

In this example there is a global variable called x, and this x is assigned the value 4.  Then fun_1 is called - fun_1 creates a local variable called x and assigns it the value 3.  Then fun_1 calls fun_2 ... which prints x.  At this point, the currently executing function (fun_2) does not have a local variable called x, because it does not assign x a value.  So in fun_2, the reference to x is to the global variable x, and the value printed is 4
When fun_2 returns to fun_1, x refers to the local variable which has the value 3.  Finally fun_1 returns to the mail program, where x refers to the global variable

Notice that fun_2 would cause an error message if we used it in a program where there was no global variable called x.  This is highly undesirable - we want functions to be self-contained and fully functional as long as they are provided with appropriate parameter values.  For this reason, a general rule of good program design is to avoid writing functions that refer to global variables.

However, there are exceptions.  For example, we might be writing a program that interacts repeatedly with the user, and addresses her by name.  We could pass the user's name as a parameter to every one of the functions, or we could make user_name a global variable and refer to it in each function.

def well():
    print user_name+", you are doing well"

def try():
    print user_name+," you should try harder"

def meet():
    print "I am glad to meet you, "+user_name

# main program

user_name = raw_input("Please enter your name ")
meet()


In general, even when we can justify writing functions that refer to global variables, it is considered bad practice to write functions that attempt to change the value of a global variable.  This is why Python automatically creates a local variable whenever a function contains an assignment statement (as in the example above when fun_1 assigns a value to x).   However, even this rule can be broken.  If it ABSOLUTELY necessary to give a function the ability to change a global variable, the function must contain the line "global <variable name>".  For example

def fun_3():
    global x
    print x
    x = 10
    print x

# main program
    x = 5
    print x
    fun_3()
    print x


In fun_3, all references to x, including the line "x = 10", refer to the global variable called x.


The bottom line is, unless there is an extremely good reason otherwise, functions should not make any reference to global variables.


Dictionaries

A dictionary is a collection of pairs of data objects.  In each pair, the first element is called the key and the second element is called the value.  Basically, we can treat a Python dictionary as a look-up table.  When we add a pair to the dictionary, we can use the key to retrieve the value.

For example

my_dict = {
                    "Popeye" : "sailorman",
                    "Clark Kent" : "reporter",
                    "Peter Parker" : "journalist",
                    "Bugs Bunny" : "sadist"
                }

creates a dictionary containing four pairs.  In each pair, the character's name is the key and their profession is the value.

We can access the values stored in the dictionary using syntax that looks a lot like the syntax for accessing a list: for example

job =  my_dict["Clark Kent"]

for k in my_dict:
    print k , my_dict[k]

We can add new pairs to the dictionary:

my_dict["Bruce Wayne"] = "millionaire playboy"

or change existing values

my_dict["Bugs Bunny"] = "psychopath"

We can create an empty dictionary in two different ways:

dict1 = {}

dict2 = dict()


The keys in a dictionary do not have to all be of the same type - they can be numbers, string, or some of each (in fact they can be any non-mutable object, which basically just rules out lists and some other things we haven't seen yet).  The values can also be of mixed types, and can be any type of object at all.  In your lab you will use a dictionary in which the values are embedded dictionaries.  As an all-in-one example, consider this

crazy_dict = {}

crazy_dict["William"] = "The conqueror"                    # string key, string value
crazy_dict[42] = "the answer"                                    # number key, string value
crazy_dict[10] = 2.544                                              # number key, number value
crazy_dict["alpha"] = [1,2,"Cheerios",8]                     # string key, list value
crazy_dict[-99] = { 7 :  "seas" , "agent" : 86}              # number key, dictionary value


Of course in practice the keys are usually all of the same type (all numbers, all strings, etc) and the values are also all of the same type (though often of a different type than the keys).

If you print a dictionary (as in the code fragment above) you will see that the keys and values are not stored in any predictable order - not ascending order (so why call it a dictionary?) and not in the order in which they are added.  The order is not random - but it is the result of the way the dictionary is stored in memory.  It is a bad idea to think of a dictionary as a type of list in which the indices are keys that we choose, rather than integers 0, 1, 2 ...  Lists are inherently positional structures, whereas dictionaries are associative structures.  We can't take a slice of a dictionary.

We can delete pairs from a dictionary using the del command

del crazy_dict[42]

will delete the pair that has 42 as its key.

We get an error if we try to access a dictionary using a non-existent key.  Fortunately we can test for this using in

if x in some_dict:
    y = some_dict[x]
else:
    y = 0

and not in

if x not in some_dict:
    y = enterbox("Enter the value to store with " + str(x))
    some_dict[x] = y
else:
    msgbox(str(x) + " is already in the dictionary")

We can retrieve a list containing all the keys in a dictionary with the keys method:

list_of_keys = crazy_dict.keys()


We can also access the key, value pairs in a dictionary using the iteritems method:

for k, v in crazy_dict.iteritems():
    print k, v




Dictionaries versus Lists

Use a list when
    - the order of the information matters (for example, the list contains jobs we are to do, in sequence)
    - the position of an item matters (for example, we may want the "middle" value)

Use a dictionary when
    -  the data items have a natural index field (such as the names of the characters stored in my_dict above)
    -  the natural index values do not form a consecutive sequence (like 0, 1, 2, ...)
    -  the order of the stored information is unimportant