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.
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.
call by value: when a parameter value is a number, boolean, or string, the data value is copied. That is, when calling the function the value is copied to a new memory location and the parameter entry in the namespace points to this new copy. We therefore say that the parameter is call by value.
call by reference: when a parameter is a list or dictionary, the newly created namespace entry points to the existing data value, which is not copied. This causes the data value to be shared, so that changes to the list or dictionary are reflected in the actual value and are visible to other variables which also point to this list or dictionary. In this case we say that the parameter is call by reference.
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]
.
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.
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.
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.)
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.
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).