Sep 25

Lecture Overview

Namespaces

Recall the rules for namespaces from the previous lecture:

There is two more rules which I didn't mention last time, which are the following:

Many languages use rules similar to the above, and when they do we say that the programming language is lexically scoped. Although there are variations and differences between languages, if you are interested PEP-3104 describes these differences and why the python developers made the choices they did.

Call by value/reference

While the rules above describe completely the semantics of python variables, as humans we apply the rules in a couple of common patterns. We try and stick to these patterns to help make our code easier to read and write, especially when many people are working together on a program. You could think of these patterns as the spirit of the rules.

When calling a function, entries in the new namespace are created for each parameter. Remember that these namespace entries have a value which is just an address in memory. Therefore, just like I described last time, these entries in the namespace can either point to a copy of the value or to the value itself.

def xxx(somenum, somelist):
    print(somenum)
    somenum = 12
    print(somenum)

    somelist.append(66)

z = 100
L = [1, 2, 3]
xxx(z, L)
print(z)
print(L)

In the above code, z is call-by-value so z is still 100 after the function. L is call-by-reference so after the call L has [1, 2, 3, 66].

Local Variables

Another very common pattern local variables, which are variables that are contained inside a function just to help compute the result and are discarded after the function returns. To do so, we create the variable within the function which causes it to go into the function's local namespace. The value will then be discarded once the function returns. Note that we can view the values of variables in lexically enclosing namespaces but cannot change them. This is a good restriction because it makes sure that at least changing the variable is kept local to the function itself.

Here is an example

x = 300

def aaa():

    y = 20

    def bbb():
        z = 80
        print("Inside bbb, x is ", x)
        print("Inside bbb, y is ", y)
        print("Inside bbb, z is ", z)

    def ccc():
        z = 100
        bbb()
        print("After bbb, z is ", z)

    ccc()

aaa()
print("After aaa, z is ", z)

Note the order of execution, first aaa is called, then ccc, then bbb. So lets consider the variable reference to x inside bbb. First, python checks the namespace for the bbb function. This namespace has no matching x, so python tries the enclosing function based on the indentation: this is the function aaa. The namespace for aaa does not have an entry for x, so the next lexically enclosing (one indentation level less) is checked. This is what we call the global or file namespace, and this namespace does have an entry for x so 300 is printed. A similar thing happens for y and 20 is printed.

Finally, inside bbb the line z = 80 is a local variable and creates a new entry in the namespace for bbb. Note there is an entry for z also in the namespace for ccc, but this entry is not affected or changed. Inside bbb, when printing the variable reference to z python looks in the current namespace and finds an entry and so prints 80.

Now after bbb returns its namespace is discarded and execution continues inside function ccc. Now we look up the variable z and see it in the current namespace with a value of 100 so 100 is printed. Between setting the variable and printing it, we had a variable named z inside the function bbb but that did not affect our namespace so we still print 100.

This above behavior for the variable z is exactly the behavior we want for local variables. If we are the programmer for the ccc function, we don't need to know or care about whatever bbb uses, we know that z will be what we expect it to be. This allows functions to be isolated, which is important if if two different people wrote bbb and ccc and just accidentally both used a variable with the same name.

Global Variables

Sometimes we would like to allow a variable in the root file namespace to be modified by a function. (This is usually disallowed, you can only change variables in your local namespace). This should be rare but python allows it using the global statement.

Breaking the spirit of the rules

There are other combinations of variable references which are allowed by the rules but are confusing and so should never be used. One breakage is that you can mask a variable. For example,

y = 3
def abc():
    y = 20
    print("Inside abc, y is ", y)
abc()
print("After abc, y is ", y)

In this case, inside abc 20 is printed and after abc 3 is printed. This is a straightforward application of the rules since the file namespace and the namespace for the function have different entires for y, but it is confusing. (It is not so confusing in this small example, but in a larger program where the abc function might have many lines and there are other functions in-between, it can be hard to see that y is reused.)

Function Default and Keyword Arguments

Python provides a few nice features to help pass parameters to functions. These introduce no new semantics and are just for convenience. See Function arguments for a description of default arguments and keyword arguments. Here is an example.

def leibniz(terms=500):
    # The leibniz computation for pi from Day 11, but now with an optional argument
    pi = 0.0

    for i in range(terms):
        denom = i * 2 + 1
        pi = pi + 4.0/denom * (-1)**i
    return pi

def triangle_area(base, height):
    # Computes the area of a triangle
    return 0.5 * base * height

print(leibniz())
print(leibniz(10))
print(leibniz(5000))

print(triangle_area(4, 9))
print(triangle_area(base=4, height=9))
print(triangle_area(height=20, base=15))

The python documentation also describes how functions can take arbitrary argument lists which is good to know about if you need it, but we won't be using this semester.

Exercises

Create a function called rect_area which takes two optional parameters width and height, both defaulting to 1, and returns the area of a rectangle of that size. Call your function four times, once giving only the width with a keyword argument, once giving only the height with a keyword argument, once giving both width and height with keyword arguments, and once giving no arguments (allowing both to be default).