Module 1.2 - Functions
Objectives
By the end of this module you will be able to:
- Enhance your understanding of how to use def to write functions, then invoke them.
- Write code with function definitions and invocations.
- Write and debug code with functions
- Explore the different ways in which parameters work.
- Identify new syntactic elements related to the above.
1.2.0 Review
Before continuing it is essential to review functions as described in Module 0.3
What to review most carefully:
- The notion of how execution starts outside a function, goes into a function, and comes back to where the function was invoked.
- When we come back to where the function was invoked, we continue execution just after the invocation.
Exercise 1.2.1
1.2.1 Simple Function Example
Consider this program:
def increment_and_print(a):
= a + 1
a print(a)
= 5
i
increment_and_print(i)
= 6
j increment_and_print(j)
Exercise 1.2.2
Explanation:
- Let’s start by distinguishing between a function definition (which merely tells Python what the function is about), and invocation (which asks Python to execute the function at that moment):
- A function invocation is also referred to as a function call.
- Now let’s peer into what constitutes a definition:
- In the above case, the function
increment_and_print
has only one parameter calleda
. - In the future, we’ll see that a function can have several parameters, separated by commas. For example
def increment_and_print(a, b, c):
- Next, let’s examine how execution proceeds, starting with what happens when a function is invoked:
- Once execution is inside the function:
- Next, execution moves further into the second invocation:
- The code inside the function now executes (again):
Did you notice that neither
i
norj
was affected by the incrementing ofa
? This is because the value ini
was copied into the freshly-createda
at the moment the function was invoked. Even thougha
got incremented, that did not affecti
. Variables likea
that appear in the parentheses of a function definition are called parameters.What does a function do with its parameters? Think of the parameters as variables that can be used as regular variables for any purpose. For example, consider this program:
def print_from_one_to(a):
print('Printing between 1 and ', a)
for i in range(1, a+1):
print(i)
5)
print_from_one_to(6) print_from_one_to(
Here, we used the parameter
a
in setting the upper limit of a for-loop.Important: When a function is defined with a parameter, the intent is that some code outside the function will set the value of the parameter. Thus, it would be allowed but technically defeat the purpose to write:
func_example2a
def print_from_one_to(a):
= 5
a print('Printing between 1 and ', a)
for i in range(1, a+1):
print(i)
5)
print_from_one_to(6) print_from_one_to(
Yes, this runs, but the whole point is for some other code to tell the function: - Here is some value for a
- The function will perform actions on that value of a
- If we pass a different value of a
, the function will do something different
And so when we write:
func_example2b
def print_from_one_to(a):
print('Printing between 1 and ', a)
for i in range(1, a+1):
print(i)
5) # We're telling the function that a = 5
print_from_one_to(6) # We're now telling the function to use a = 6 print_from_one_to(
Exercise 1.2.3
Exercise 1.2.4
1.2.2 Multiple Parameters
Remember Pythagoras? We know his famous result for right triangles:
\(a^2 + b^2 = c^2\)
A Pythagorean triple is any group of three integers where the squares of the first two add up to the square of the third: \(3^2 + 4^2 = 5^2\) .
We’ll now write code to check whether a trio of numbers is indeed a Pythagorean triple:
def check_pythagorean(a, b, c):
if (a*a + b*b) == c*c:
print('yes')
else:
print('no')
3, 4, 5)
check_pythagorean(5, 12, 13)
check_pythagorean(6, 8, 20) check_pythagorean(
Note: This time, we’ve defined a function that takes three parameters:
def check_pythagorean (a, b, c):
Notice the commas separating the three variables. Consider the first invocation:
3, 4, 5) check_pythagorean(
The invocation also uses commas to separate arguments. When the function runs, the first argument (3) becomes a
, the second argument (4) becomes b
, and the third argument (5) becomes c
. This is based on the order of the arguments- their relative position- in both the function definition and the function call.
Exercise 1.2.5
1.2.3 Return Values
We have written functions that take values, do things and print. We get a whole new level of programming power, when functions can compute something and return something to the invoking code.
Here’s an example:
def padd(a, b):
total = 0
if a >= 0:
total += a
if b >= 0:
total += b
return total
x = padd(-5, 6)
print(x)
Exercise 1.2.6
- First, execution begins after the function definition is read by Python:
Remember, in an assignment statement, the right side of the expression on Line 1 is executed first:
padd(-5, 6)
. The result of invoking this function somehow results inx
getting a value stored inside it. (We’ll see how, shortly.)Next, execution goes into the function:
- When the return statement executes, the value returned (the value of
total
) gets copied intox
- One way to think about it: When we see
x = padd(-5, 6)
then think of execution going to the functionpadd
with arguments (-5, 6) - After it executes, it returns the value in one of its variables: in this case, 6
- That, behind the scenes, replaces the function invocation:
x = 6
And thus, the value 6 gets copied intox
.
Exercise 1.2.7
Let’s return to our earlier example and use the padd()
function in different ways:
Exercise 1.2.8
Let’s examine each of the last three statements:
The first one:
print(padd(-5, 6))
In this case: The invocation to padd()
occurs first: (padd(-5, 6)
- That function executes and returns 6. So, we can think of the result of that as:
print(6)
Which sends 6 to theprint()
function.
Next, consider:
= padd(padd(-5,6), 7) x
In this case: The innermost padd()
is first invoked: padd(-5,6)
. The function returns 6, and so, we could think of the result as x = padd(6, 7)
- This now results in another invocation to
padd()
with parameter valuespadd(6, 7)
. - The function now executes again and returns 13. The result is
x =13
Finally, consider the third example:
print(padd(padd(-5,6), padd(5,-6)))
In this case, there are two innermost invocations: padd(-5, 6)
and padd(5,-6)
.
- The left argument,
padd(-5,6)
, is called first, resulting in a return value of 6:print( padd(6, padd(5,-6) ) )
- Then, the right argument,
padd(5, -6)
is called, resulting in a return value of 5:print( padd( 6, 5) )
- Now, these results (6 and 5) are sent once again to the third invocation:
print(padd( 6, 5 ) )
- This returns 11. print(``)
- And so 11 gets sent to
print
, which prints it.
1.2.4 Multiple Returns In A Function
Take a moment to go back up and quickly glance through the Pythagorean example.
Now consider this rewrite:
def check_pythagorean(a, b, c):
if a*a + b*b == c*c:
print("The 'if' condition is met")
return 'yes'
else:
print("The 'if' condition is not met")
return 'no'
result = check_pythagorean(3, 4, 5)
print(result)
result = check_pythagorean(5, 12, 13)
print(result)
result = check_pythagorean(6, 8, 20)
print(result)
Exercise 1.2.9
Exercise 1.2.10
1.2.5 Parameter and Argument Names
Consider this example:
def subtract(a, b):
= b - a
c return c
= 5
x = 6
y = subtract(x, y) z
We refer to a
and b
as parameters in the definition of the function: def subtract(a,b):
When the function is called, subtract(x, y)
: we use the term arguments for x
and y
.
The names given to parameters have no relation to the names used for arguments: In the above case:
Consider this variation:
def subtract(a, b):
= b - a
c return c
= 5
c = 6
a = subtract(a, b) b
This is valid code, but it is confusing! The a,b,c that are the parameter variables (inside the function) are different from the a,b,c variables below (outside the function). If you aren’t sure, it’s safest to use different names.
1.2.6 Using Parameter Variables
Generally, the purpose of parameter variables is this:
Consider this example:
def example_func(a, b):
= 2*a - b
c print(c)
3, 4)
example_func(= 6
x 8) example_func(x,
From the point of view of the function, example_func
thinks “Someone is going to put values into my variables a
and b
, and then I’ll do stuff like calculate and print”. From the point of view of code that is using the function, as in
3, 4) example_func(
This is saying “we’ll set the function’s first parameter (a
)to 3, and second parameter (b
) to 4, and then let the function do its thing”. Now, functions can use their parameter variables just like any other variable and change their values, as in:
def change_vars(p, q):
print(p)
= p + 3*q # We're changing p here
p print(p)
= p + q
r print(r)
= 6
x = 7
y change_vars(x, y)
Because the value in x
gets copied into the variable p
, the value in x
does not get changed even though the function changes the value in p
.
Exercise 1.2.11
1.2.7 Functions and Lists
Can a function receive a list as parameter? Yes. Can it return a list? Yes. Is this useful? Yes.
Let’s start with a list as parameter:
def compute_total(B):
= 0
total for k in B:
+= k
total return total
= [1, 3, 5, 7]
A = compute_total(A)
t print(t)
- Trace the execution of the above program, including loop iterations.
Exercise 1.2.12
Exercise 1.2.13
Next, let’s look at an example where a list is returned:
def build_odd_list(n):
= []
L for i in range(0, n):
= 2*i + 1
k
L.append(k)return L
print(build_odd_list(5))
- Try to trace through the above.
Exercise 1.2.14
Execution begins with print(build_odd_list(5))
This results in:
Once the return occurs, we could think of this as: print( [1, 3, 5, 7, 9] ) This gets sent to print, which prints. One could make this more explicit:
Next, let’s look at a list example with multiple returns:
def find_first_negative(A):
for k in A:
if k < 0:
# First try print(k) here
return k
# Then try print(k) here
return 0
= [1, 2, -3]
B = [1, 2, 3]
C
print(find_first_negative(B))
print(find_first_negative(C))
Exercise 1.2.15
Exercise 1.2.16
1.2.8 Returns That Don’t Return Anything
Consider this program:
def print_first_negative(A):
for k in A:
if k < 0:
print('Found: ',k)
return
print('No negatives found')
= [1, 2, -3]
B
print_first_negative(B)= [1, 2, 3]
C print_first_negative(C)
Exercise 1.2.17
Let’s point out: Whenever a return
statement is executed in a function, execution exits the function right away. When a return
statement does not return a variable’s value, execution leaves the function but does not give a value back to the invoking code. Thus the return
here:
def print_first_negative(A):
for k in A:
if k < 0:
print('Found: ',k)
return
print('No negatives found')
does not return a value but merely causes execution to leave the function and go back to just after where the function was invoked. Although it’s not needed, one could return at the end of a non-value-returning function:
def print_first_negative(A):
for k in A:
if k < 0:
print('Found: ',k)
return
print('No negatives found')
return
Why do we have multiple return’s in a function? Why not always wait until execution reaches the end of a function?
- It is very useful to be able to return from anywhere in a function’s code.
- As soon as the function’s “job” is done (example: finding the first negative), we want to leave the function.
1.2.9 A Difference Between Lists and Other Parameters
Consider this program:
def swapint(a, b):
temp = a
a = b
b = temp
print(a, b)
x = 5
y = 6
swapint(x, y)
print(x, y) # Will this print 5 and 6, or 6 and 5?
Exercise 1.2.18
Let’s explain:
- Execution of the program begins with the line
= 5 x
- When the function is called soon after:
swapint(x, y)
- Execution enters the function with the values in
x
andy
copied intoa
andb
:
temp
is a temporary variable created inside of the function:
Then, by the time we reach the print statement:
The values in a
and b
do indeed get swapped, but this does not affect x
and y
because they are actually different variables.
Now, consider a similar program with lists:
Exercise 1.2.19
Let’s see what’s going on:
- List variables are fundamentally different from numeric variables.
- Think of a list variable has having a reference ID tothe actual list contents:
- This is like an “address” in memory.
- If you have this “address” you can go to the list and do things with it.
- List variables actual store these addresses (which, interestingly, turn out to be numbers).
- We will draw a conceptual diagram to highlight this point:
Thus, when the function starts execution, the “references” in X
and Y
are copied into A
and B
. This means A
and B
refer to the same list contents. So, A[0]
is the same as X[0]
, for example.
Next, after executing the three lines inside the function but before returning:
Notice that temp is a regular integer.
Exercise 1.2.20
The key takeaways:
- When you send number variables to a function, they get copied, and so the function can’t “do” anything to the variables you present as arguments.
- If you send a list, a function can change the contents. This means you need to be careful about intent when writing functions that involve larger entities like lists. (There are other such “grouped data” entities, called objects .)
- If the intent is to change contents, that’s fine, we should know that.
Exercise 1.2.21
1.2.10 Calling Functions From Functions
The code inside functions can be like regular code that’s outside functions. In particular, code inside functions can call (invoke) other functions.
For example:
def increment(a):
= a + 1
b return b
def increment_twice(c):
= increment(c)
d = increment(d)
e return e
= 5
x = increment_twice(x)
y print(y)
Note: The program starts execution at the line
= 5 x
- When
increment_twice(x)
is called, we enter the functionincrement_twice()
with the variablec
set to 5 (copied fromx
). - The next line,
d = increment(c)
results in a call toincrement()
with the value 5 copied into parametera
. - Whe code in
increment()
executes, resulting in 6 being returned. The returned value 6 is stored ind
. increment()
is called again with the value ind
(now 6) copied intoa
. The code inincrement()
executes, returning 7.- Execution continues in the
increment_twice()
function and the value 7 is stored ine
. - Finally
increment_twice()
completes execution and returns the value ine
, which is 7. This value is stored iny
. - Execution continues from there to the
print
.
We can shorten the above code by writing:
def increment(a):
return a + 1
def increment_twice(c):
return increment(increment(c))
= 5
x = increment_twice(x)
y print(y)
- Notice that one can return the result of an expression:
return a + 1
- In this case, the calculation is an arithmetic expression. One can also have the result of a function call itself be returned:
def increment_twice(c):
return increment(increment(c))
- Afterwards, it’s possible to do this:
print(increment(increment(c)))
- Here, the result of the inner
increment()
call is sent again toincrement()
- In the same vein, we can, instead of printing, execute a
return
return increment(increment(c))
You do not have to use such shortcuts. Some shortcuts are an advanced topic and may in fact make your code harder to read, and harder to fix mistakes in.
Exercise 1.2.22
1.2.11 Data Analysis
Armed with our new ability to work with functions and lists, we will see how easy it is to compute basic statistics with data.
Exercise 1.2.23
With that, consider this partially complete program:
def find_smallest(A):
= A[0]
smallest for k in A:
if k < smallest:
= k
smallest return smallest
def find_largest(A):
# Insert your code here:
def find_span(A):
= find_smallest(A)
smallest = find_largest(A)
largest = largest - smallest
span return span
= [-2.3, -1.22, 1.6, -0.5, 1.4, 2.5, -3.32, 11.03, 2, 2, -1.4]
data print('span: ', find_span(data))
Exercise 1.2.24
Next, let’s write some (partially complete) code for summary statistics:
import math
def compute_mean(A):
# Insert your code here:
def compute_std(A):
= compute_mean(A)
mean = 0
total for k in A:
+= (k-mean)**2
total = math.sqrt(total / len(A))
std return std
= [-2.3, -1.22, 1.6, -10.5, 1.4, 2.5, -3.32, 11.03, 2, 2, -1.4]
data print('mean =', compute_mean(data))
print('standard deviation =', compute_std(data))
Note: The mean of a list of numbers is the total (add the numbers up) divided by “how many numbers there are” in the list. It is sometimes called the “average” (less precise) or “arithmetic mean” (more precise). We will simply call it the “mean.”
The standard deviation is more involved: Consider data like:
10, 20, 30, 40, 50
This has a mean of 30. Here, a different set of data that also has a mean of 30.
28, 29, 30, 31, 32
- Clearly, the second set of data is very bunched up around 30 while the first is more spread out.
- The standard deviation is a measure (a single number) that describes how much the values vary from the mean.
- The more spread out, the higher the standard deviation.
- To calculate it, we take the difference of each data point from the mean, square it (to make it always positive), then add all of these up. This gives something called the variance.
- Because we squared our values, the units of variance often do not make sense. For instance, if we are working with temperature in degrees Fahrenheit, the variance is in units of “degrees Fahrenheit squared.”
- We take the square root of the variance to obtain the standard deviation, which will have the same units as the original measurements.
- If you have never seen this before, it’s worth computing by hand with the data above, just so you understand how it looks on paper. Then compare with the results from the program.
Exercise 1.2.25
Next, let’s tackle the more challenging problem of identifying outlying values (or outliers): Take a closer look at the data:
data = [-2.3, -1.22, 1.6, -10.5 , 1.4, 2.5, -3.32, 11.03, 2, 2, -1.4]
We see that two data values seem to be far outside the range of the other data. Suppose we use the following approach to identifying outliers:
- Compute the mean and standard deviation.
- Identify those data that lie further than two standard deviations away from the mean.
- Call these outliers. easy to work with. Let’s write a function to do this:
def find_outliers(A):
= compute_mean(A)
mean = compute_std(A)
std = []
outliers for k in A:
if k < (mean - 2*std):
outliers.append(k)elif k > (mean + 2*std):
outliers.append(k)return outliers
Exercise 1.2.26
1.2.12 A Function That Calls Itself
Recursion is a somewhat advanced topic (not on any exam).
Consider this example:
def factorial(n):
# print(n)
if n == 1:
return 1
else:
= factorial(n-1)
m return n * m
print(factorial(3))
print(factorial(5))
Exercise 1.2.27
- It’s allowable for a function to call itself. Such a function is called a recursive function, and the resulting behavior is called recursion. The above example computes numbers like: \(1 × 2 × 3 × 4 × 5 × = 120\)
- (This ascending multiplication is called factorial.)
- For recursion to work, the successive function calls have to end to prevent an infinite runtime. In the above case,
n
eventually becomes 1. - In this case,there’s no further call to itself. Recursion is hard to understand, and will be featured in later programming courses.
- Surprisingly, many problems are solvable elegantly and efficiently using recursion.
- It is possible to do recursion improperly, in which case a program is set up to recurse forever. In this case, Python will give up after too many recursions.
1.2.13 When Things Go Wrong
In each of the exercises below, first try to identify the error just by reading. Then type up the program to confirm, and after that, fix the error.
Exercise 1.2.28
Exercise 1.2.29
Exercise 1.2.30
End-Of-Module Problems
Full credit is 100 pts. There is no extra credit.