Python,
Week 5
Functions: defining and calling them, return values, scope and the global keyword, argument types, and recursion, tied together with worked list and matrix examples.
Chapters
- 01Functions, Calls and returnParts 1-4
- 02Scope, Lifetime and globalParts 5-6
- 03Function Arguments and Types of FunctionsParts 7-8
- 04List FunctionsPart 9
- 05Sorting and Matrix Multiplication with FunctionsParts 10-11
- 06RecursionParts 12-13
- 07Try It YourselfPractice zone
- 08Week 5 Summary SheetAll parts
Functions, Calls and return
What a function is, how to define and call it, and what return does.
1.1What is a function?
In Python, a function is a group of related statements that performs a specific task. Functions help break a program into smaller, modular chunks. As a program grows larger, functions make it more organised and manageable. Furthermore, they avoid repetition and make the code reusable.
1.2Syntax of a function
def function_name(parameters): followed by the indented statement(s).
A function definition consists of these components:
- The keyword
def, which marks the start of the function header. - A function name to uniquely identify the function. Function naming follows the same rules as writing variables in Python.
- Parameters (arguments) through which we pass values to a function. They are optional.
- A colon
:to mark the end of the function header. - One or more valid Python statements that make up the function body. Statements must have the same indentation level.
- An optional
returnstatement to return a value from the function.
def sums(a, b):
c = a + b
return cdef only stores the recipe. Nothing inside the body runs until the function is called.def hello():
print("inside the function")
print("defined")defined1.3How to call a function
Once we have defined a function, we can call it from another function, a program, or even the Python prompt. To call a function we simply type the function name with the appropriate arguments.
def sums(a, b):
c = a + b
return c
print(sums(5, 6))11sums(5, 6) followed by print(sums) and says the output is 11. That is wrong: print(sums) (no brackets) prints the function object itself, as below. To see the answer, call the function inside print: print(sums(5, 6)). The same slip appears later with print(add).def sums(a, b):
c = a + b
return c
print(sums)<function sums at 0x1005bb060>The long hexadecimal number is a memory address, so it is different on every machine and every run. What matters is that the brackets make the call: sums is the function, sums(5, 6) is its result.
def sums(a, b):
c = a + b
return c
print(sums(sums(1, 2), 3))
print(sums("a", "b"))6
abBecause Python does not fix the types, the same sums adds numbers or joins strings, and a call can be used inside another call (extra).
1.4The return statement
The return statement is used to exit a function and go back to the place from where it was called. It can contain an expression, which is evaluated and returned. If there is no expression, or no return at all, the function returns the None object.
def greet(name):
print("Hello" + name + ". Morning")
print(greet("Paul"))HelloPaul. Morning
NoneNone is the returned value, because greet() directly prints the name and has no return. So print(greet("Paul")) prints the greeting (from inside the function) and then None (the returned value).
Hello Paul. Morning, with a space. The code builds the text with "Hello" + name + ". Morning", which has no space after Hello, so the real output is HelloPaul. Morning. (Write "Hello " + name to get the space.)def absolute_num(num):
if num >= 0:
return num
else:
return -num
print(absolute_num(2))
print(absolute_num(-4))2
4- A
returnends the function at once (extra). Statements after it are never run. - Several values (extra).
return a, bsends back both as a tuple, which can be unpacked into two variables.
def f(x):
return x * 2
print("never printed")
print(f(4))
def both(a, b):
return a + b, a - b
s, d = both(5, 2)
print(s, d)
print(both(5, 2))8
7 3
(7, 3)1.5How a function works
When a function is called, control jumps into the function body, runs each statement in order, and, once it hits a return or the end of the function, jumps back to right after where it was called.
def sums(a, b):
print(" inside: adding", a, "and", b)
c = a + b
return c
print("1. start")
r = sums(5, 6)
print("2. back, r =", r)
print("3. end")1. start
inside: adding 5 and 6
2. back, r = 11
3. end| Step | Where | What happens |
|---|---|---|
| 1 | main | prints "1. start" |
| 2 | main → sums | call sums(5, 6): control jumps in; a = 5, b = 6 |
| 3 | sums | prints the "inside" line, computes c = 11 |
| 4 | sums → main | return c jumps back; the call becomes the value 11, stored in r |
| 5 | main | prints "2. back, r = 11" and "3. end" |
1.6Going deeper
- Name, parameter, argument (extra). In
def sums(a, b)the namesaandbare parameters. In the callsums(5, 6)the values 5 and 6 are arguments. The source uses "parameters (arguments)" for both. - Define before you call (extra). The
defline must have run before the call, so functions are usually defined at the top of the program.
print(early(2))
def early(x):
return xNameError: name 'early' is not defined1.7Exam tips and common mistakes
- For every function in a question first decide: does it print, return, or both?
- When asked for the output of
print(f(...))write the lines printed insideffirst, then the returned value.
- Forgetting the colon after the header, or the indentation of the body.
- Writing
print(f)when you meantprint(f(...)). - Using
printinside a function and then expecting a value back. - Writing statements after
returnand expecting them to run.
Practice questions (graded-assignment style)
Multiple Choice Question (MCQ): choose the one correct option.
What does the program print?
def show(a):
b = a + 1
x = show(6)
print(x)The function returns nothing, so the call is None.
def show(a):
b = a + 1
x = show(6)
print(x)Noneshowhas noreturn, so the call givesNoneandxisNone.- The function itself prints nothing, so the only line printed is the
Nonefromprint(x). - Answer: option 2.
Numerical Answer Type (NAT).
What does the program print?
def sums(a, b):
c = a + b
return c
print(sums(5, 6))Call the function inside print.
def sums(a, b):
c = a + b
return c
print(sums(5, 6))11sums(5, 6)returns5 + 6 = 11.- Answer: 11
Multiple Choice Question (MCQ): choose the one correct option.
What does the program print?
def absolute_num(num):
if num >= 0:
return num
else:
return -num
print(absolute_num(-4))absolute_num returns the positive version of the number.
def absolute_num(num):
if num >= 0:
return num
else:
return -num
print(absolute_num(-4))4-4is negative, so theelsebranch returns-(-4) = 4.- Answer: option 3.
Multiple Choice Question (MCQ): choose the one correct option.
What does the program print?
def f(x):
return x * 2
print("never printed")
print(f(4))A return ends the function.
def f(x):
return x * 2
print("never printed")
print(f(4))8- The function returns
8at thereturnline; theprintafter it never runs. - Answer: option 1.
Numerical Answer Type (NAT).
What does the program print?
def both(a, b):
return a + b, a - b
print(both(5, 2))return a, b gives a tuple.
def both(a, b):
return a + b, a - b
print(both(5, 2))(7, 3)- The function returns the pair
(7, 3), andprintshows it with brackets. - Answer: (7, 3)
Multiple Select Question (MSQ): select all that apply.
Choose the correct statements.
Brackets make the call.
- Statement 3 is false:
print(f)shows the function object;print(f())calls it. - Answer: options 1, 2 and 4.
Colon and indented body
Defining does not run it
Brackets make the call
print(f) shows the object
No return gives None
return a, b gives a tuple
No repetition
Reusable
Return: jump back
The call becomes the value
return gives back
Do not mix them up
Scope, Lifetime and global
Which variables a function can see, how long they live, and how global changes things.
Where can a variable be used, and for how long does it live? These two ideas, scope and lifetime, explain most surprises people have with functions.
2.1Scope and lifetime of variables
The scope of a variable is the portion of a program where the variable is recognised. Parameters and variables defined inside a function are not visible from outside the function, so they have a local scope.
The lifetime of a variable is the period throughout which it exists in memory. The lifetime of variables inside a function is as long as the function executes. They are destroyed once we return from the function. So a function does not remember the value of a variable from its previous calls.
def my_func():
x = 10
print("Value inside the function:", x)
x = 20
my_func()
print("Value outside function:", x)Value inside the function: 10
Value outside function: 20The outside value is still 20. The x inside my_func() is a different variable (local to the function) from the x outside. They have the same name but different scopes. (The source says the function "changed" x to 10; more exactly, the assignment created a new local x and left the outer one alone.)
A function forgets between calls
def count():
c = 0
c += 1
return c
print(count())
print(count())
print(count())1
1
1Each call starts with a fresh c = 0, so the answer never grows. To keep a count between calls it has to live outside the function (next section).
2.2Reading a variable from outside
Variables outside a function are visible from inside: they have global scope. We can read these values inside the function, but we cannot change (write) them.
rate = 0.18
def with_tax(price):
return price + price * rate
print(with_tax(100))118.0rate is read inside the function without any trouble. But the moment a function assigns to a name, Python treats that name as local for the whole function (extra). That makes the following program fail:
x = 5
def f():
x = x + 1
return x
print(f())UnboundLocalError: cannot access local variable 'x' where it is not associated with a valueInside f, x = x + 1 makes x local, and the local x has no value yet when the right-hand side is read. The cure is the global keyword.
2.3The global keyword
To modify the value of a variable outside the function, it must be declared as a global variable using the keyword global.
def myFunction1():
global x
x = x * 2
print("Value of x in function 1", x)
def myFunction2():
global x
x = x * 3
print("Value of x in function 2", x)
x = 5
print("Value of x before function call", x)
myFunction1()
myFunction2()
print("Value of x after function call", x)Value of x before function call 5
Value of x in function 1 10
Value of x in function 2 30
Value of x after function call 30| Step | Statement | x after |
|---|---|---|
| 1 | x = 5 | 5 |
| 2 | myFunction1(): x = x * 2 | 10 |
| 3 | myFunction2(): x = x * 3 | 30 |
global xis written inside the function, beforexis used there.- Only assignment needs it (extra). A list defined outside can be changed with methods like
appendwithoutglobal, because the name is not being re-assigned. - Prefer parameters and return (extra). Passing values in and returning results keeps functions independent and easier to test. Use
globalsparingly.
items = []
def add_item(x):
items.append(x)
add_item(1)
add_item(2)
print(items)[1, 2]2.4Going deeper
- Parameters are local too (extra). Changing a parameter inside the function does not change the caller's variable (for a number or string). Changing the contents of a list passed in does change it, as Chapter 5 shows.
- Name lookup order (extra). Python looks for a name first inside the function, then outside. A local name hides a global one with the same name.
def bump(n):
n = n + 1
return n
x = 10
print(bump(x))
print(x)11
102.5Exam tips and common mistakes
- For scope questions list the variables in two columns: inside the function, outside. Same name does not mean same variable.
- A
globalline is the clue that the outside variable is meant to change.
- Expecting a local assignment to change the outside variable.
- Expecting a function to remember a local variable from its last call.
- Forgetting
globaland then gettingUnboundLocalError.
Practice questions (graded-assignment style)
Multiple Choice Question (MCQ): choose the one correct option.
What does the program print?
x = 5
def show():
x = 100
print("Inside:", x)
show()
print("Outside:", x)The function makes its own local x.
x = 5
def show():
x = 100
print("Inside:", x)
show()
print("Outside:", x)Inside: 100
Outside: 5- The assignment inside
showcreates a localx; the globalxis untouched. - Answer: option 2.
Numerical Answer Type (NAT).
What does the program print?
n = 0
def f():
global n
n += 1
f()
f()
f()
print(n)Each call adds 1 to the same variable.
n = 0
def f():
global n
n += 1
f()
f()
f()
print(n)3- Three calls add 1 each time to the same global
n: 0 → 1 → 2 → 3. - Answer: 3
Multiple Choice Question (MCQ): choose the one correct option.
What does the program print?
def count():
c = 0
c += 1
return c
print(count(), count(), count())A local variable is created fresh on each call.
def count():
c = 0
c += 1
return c
print(count(), count(), count())1 1 1cis reset to 0 at the start of every call, so each call returns 1.- Answer: option 2.
Numerical Answer Type (NAT).
What does the program print?
def f():
global x
x = x * 2
def g():
global x
x = x * 3
x = 5
f()
g()
print(x)First doubles, then triples.
def f():
global x
x = x * 2
def g():
global x
x = x * 3
x = 5
f()
g()
print(x)305 × 2 = 10, then10 × 3 = 30.- Answer: 30
Multiple Choice Question (MCQ): choose the one correct option.
What does the program print?
def bump(n):
n = n + 1
return n
x = 10
print(bump(x), x)Changing a number parameter does not change the caller's variable.
def bump(n):
n = n + 1
return n
x = 10
print(bump(x), x)11 10bump(x)returns 11, but the variablexoutside is still 10.- Answer: option 2.
Multiple Select Question (MSQ): select all that apply.
Choose the correct statements.
Local variables vanish when the function returns.
- Statement 2 is false: local variables are destroyed on return.
- Answer: options 1, 3 and 4.
Not visible outside
Destroyed on return
Not writable by default
Same name is not same variable
Written inside the function
Needed for assignment only
No memory between calls
when you assign without global
Return results out
Function Arguments and Types of Functions
Wrong counts, default and keyword arguments, and the kinds of functions.
How many values must a call give, and in what order? This chapter covers wrong argument counts, default arguments, keyword arguments, and the different kinds of functions you can call.
3.1Types of function arguments
def add(a, b):
ans = a + b
return ans
print(add(4, 2))6Here add() has two parameters. Since we called it with two arguments, it runs smoothly. If we call it with a different number of arguments, the interpreter shows an error message.
One argument
def add(a, b):
ans = a + b
return ans
print(add(4))TypeError: add() missing 1 required positional argument: 'b'No arguments
def add(a, b):
ans = a + b
return ans
print(add())TypeError: add() missing 2 required positional arguments: 'a' and 'b'Too many arguments (extra)
def add(a, b):
ans = a + b
return ans
print(add(1, 2, 3))TypeError: add() takes 2 positional arguments but 3 were givenadd(4, 2) followed by print(add) and shows 6 as the output. As in Chapter 1, print(add) would print the function object. The value 6 comes from print(add(4, 2)).Python has different kinds of function arguments to deal with such situations.
3.2Default arguments
Function arguments can have default values. We give a default to an argument with the assignment operator =.
def add(a, b=10):
ans = a + b
return ans
print(add(4))
print(add(2, 3))14
5Here a has no default and is required in a call. b has the default 10, so it is optional: if a value is provided, it overwrites the default.
Any number of arguments can have a default value. But once an argument has a default, all the arguments to its right must also have defaults: non-default arguments cannot follow default arguments.
def add(a=10, b):
ans = a + b
return ansSyntaxError: parameter without a default follows parameter with a defaultnon-default argument follows default argument. Newer Python versions say parameter without a default follows parameter with a default, as in the real output above. It is the same SyntaxError, so both mean the rule was broken.3.3Keyword arguments
When we call a function with values, they are assigned to the parameters by position. Python also allows keyword arguments: name=value in the call. This way the order of the arguments can be changed. In add(4, 2) the value 4 goes to a and 2 to b. The calls below are all valid:
def add(a, b):
ans = a + b
return ans
print(add(a=2, b=4))
print(add(b=9, a=3))
print(add(4, b=2))6
12
6- We can mix positional and keyword arguments, but keyword arguments must follow positional ones. A positional argument after a keyword argument is an error.
- Positional aur keyword arguments mila sakte hain, par keyword arguments positional ke baad aane chahiye. Keyword ke baad positional argument error hai.
def add(a, b):
ans = a + b
return ans
print(add(a=4, 2))SyntaxError: positional argument follows keyword argument- Extra: a name cannot get two values.
add(1, a=2)givesaa value twice. - Extra: unknown keywords fail.
add(1, c=2)uses a name the function does not have.
def add(a, b):
ans = a + b
return ans
print(add(1, a=2))TypeError: add() got multiple values for argument 'a'def add(a, b):
ans = a + b
return ans
print(add(1, c=2))TypeError: add() got an unexpected keyword argument 'c'Why keywords help. In a call like address(pin="600001", city="Chennai", state="TN") the names make the call readable, and the order no longer matters.
3.4Types of functions
| Kind | Examples | Where it comes from |
|---|---|---|
| In-built functions | print(), input(), len() | always available |
| Library functions | log(), sqrt(), random(), randrange(), calendar(), month() | need an import first |
| String methods | upper(), lower(), strip(), count(), index(), replace() | called on a string with a dot |
| User-defined functions | the ones you write with def | your own code |
def square(x):
sqr = x ** 2
return sqr
print(square(5))25import math
import random
print(len([4, 8, 2]))
print(math.sqrt(16))
random.seed(1)
print(random.randrange(1, 7))
s = " Hello World "
print(s.strip().upper())
print(s.count("o"), s.strip().index("W"), s.replace("World", "Python").strip())3
4.0
2
HELLO WORLD
2 6 Hello PythonNotice the difference in how they are called: len(x) and square(5) are plain function calls; math.sqrt(16) uses a module name; s.upper() is a method called on the object s. The random.seed(1) line only makes the random number repeatable (extra).
3.5Going deeper
- Defaults are evaluated once (extra). Use simple values (numbers, strings,
None) as defaults. A list as a default (def f(x, l=[])) is created only once and is shared by every call, which causes surprising bugs. - Which style when (extra). Use defaults for settings people rarely change, and keywords when a call has many arguments of the same type.
def add_to(x, l=[]):
l.append(x)
return l
print(add_to(1))
print(add_to(2))[1]
[1, 2]3.6Returning several values, *args, docstrings and two traps with lists
A. Returning more than one value
return a, b returns one tuple holding both values. The caller usually unpacks it into two names. A function that reaches its end without return gives back None.
def min_max(a, b):
if a < b:
return a, b
return b, a
print(min_max(3, 2)) # one tuple
lo, hi = min_max(3, 2) # unpacked
print(lo, hi)
def show(x):
print(x * 2) # prints, but returns nothing
r = show(0) # prints 0
print(r) # ... and r is None(2, 3)
2 3
0
Noneprint only shows a value on the screen; return hands it back so the caller can use it. A function used inside an expression (3 + f(2)) must return, otherwise the expression meets None and fails with a TypeError.B. Any number of arguments: *args
A parameter written *nums collects all the extra positional arguments into a tuple called nums. The function then works for two arguments, ten, or none:
def total(*nums):
s = 0
for n in nums:
s = s + n
return s
print(total()) # no arguments: nums is ()
print(total(1, 2, 3))
def show_args(*nums):
return nums
print(show_args(1, 2, 3))0
6
(1, 2, 3)C. Docstrings
A string placed on the first line inside a function is its docstring: a description that tools (and help()) can display. It does not run, and unlike a # comment it is stored in f.__doc__.
def square(x):
"""Return the square of x."""
return x * x
print(square.__doc__)
print(square(5))Return the square of x.
25D. Trap 1: a list default argument is created once
Default values are evaluated once, when the def line runs. A default list is therefore one shared list that keeps growing between calls:
def add_item(x, items=[]): # the list is created once
items.append(x)
return items
print(add_item(1))
print(add_item(2)) # the same list as before
print(add_item(3, [9])) # passing your own list avoids the sharing
print(add_item(4))[1]
[1, 2]
[9, 3]
[1, 2, 4]The fix is to use None as the default and build a fresh list inside:
def add_item(x, items=None):
if items is None:
items = []
items.append(x)
return items
print(add_item(1))
print(add_item(2))[1]
[2]E. Trap 2: a function can change the caller's list, but not by rebinding
A list passed to a function is the same list (the parameter is another name for it). Changing it with append, sort, l[i] = ... shows up outside. l = l + [4] builds a new list and only rebinds the local name, so the caller's list is untouched. This is the "two names, one list" idea of week 4, now across a function call.
def change_in_place(l):
l.append(4)
def rebind(l):
l = l + [4] # a new list; the caller never sees it
a = [1]
change_in_place(a)
b = [1]
rebind(b)
print(a, b)[1, 4] [1]3.7Exam tips and common mistakes
- Match arguments to parameters: first by position, then by keyword. Parameters left over take their defaults.
- A "which call is invalid?" question usually hides a positional-after-keyword or a missing/extra argument.
- Putting a default parameter before a non-default one.
- Giving a positional argument after a keyword argument.
- Calling with the wrong number of arguments.
Practice questions (graded-assignment style)
Multiple Choice Question (MCQ): choose the one correct option.
What does the program print?
def add(a, b=10):
return a + b
print(add(4))b falls back to its default.
def add(a, b=10):
return a + b
print(add(4))14add(4)givesa = 4and the defaultb = 10, so the result is14.- Answer: option 2.
Numerical Answer Type (NAT).
What does the program print?
def add(a, b=10):
return a + b
print(add(b=9, a=3))Keywords match names, not positions.
def add(a, b=10):
return a + b
print(add(b=9, a=3))12b=9anda=3give3 + 9 = 12.- Answer: 12
Multiple Choice Question (MCQ): choose the one correct option.
What happens when the call runs?
def add(a, b):
return a + b
print(add(4))b has no default.
def add(a, b):
return a + b
print(add(4))TypeError: add() missing 1 required positional argument: 'b'- One required argument is missing, so Python raises a
TypeError. - Answer: option 2.
Multiple Choice Question (MCQ): choose the one correct option.
What happens when the program runs?
def add(a, b):
return a + b
print(add(a=4, 2))Keyword arguments must follow positional ones.
def add(a, b):
return a + b
print(add(a=4, 2))SyntaxError: positional argument follows keyword argument- A positional argument after a keyword argument is rejected when the program is read, as a
SyntaxError. - Answer: option 3.
Numerical Answer Type (NAT).
What does the program print?
def add(a, b=10):
return a + b
print(add(4, b=2))The positional 4 goes to a; the keyword fills b.
def add(a, b=10):
return a + b
print(add(4, b=2))6a = 4by position andb = 2by keyword:4 + 2 = 6.- Answer: 6
Multiple Select Question (MSQ): select all that apply.
Choose the correct statements.
Keyword arguments follow positional ones.
- Statement 2 is false: keyword arguments must come after the positional ones.
- Answer: options 1, 3 and 4.
Multiple Choice Question (MCQ): choose the one correct option.
What does the program print?
def f(a, b):
return a + b, a * b
x = f(2, 5)
print(x)7(7, 10)[7, 10]7 10return a, b returns one tuple; printing a tuple shows its brackets and commas.
def f(a, b):
return a + b, a * b
x = f(2, 5)
print(x)(7, 10)- and packed as
(7, 10). Option 4 would come fromprint(*x)ora, b = f(2, 5)followed byprint(a, b). - Answer: option 2.
Multiple Choice Question (MCQ): choose the one correct option.
What happens when this program runs?
def double(x):
print(x * 2)
y = 10 + double(3)
print(y)6 then 16166, then raises a TypeError6 then Nonedouble prints but returns None, so 10 + double(3) is 10 + None.
def double(x):
print(x * 2)
y = 10 + double(3)
print(y)TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'- The call runs first and prints
6. Then10 + Noneis not allowed, so the program stops with aTypeErrorand never reaches the lastprint. - Changing
print(x * 2)toreturn x * 2would give16. - Answer: option 3.
Numerical Answer Type (NAT).
What does the program print?
def count_big(*nums):
c = 0
for n in nums:
if n > 10:
c += 1
return c
print(count_big(5, 12, 30, 7, 11))nums is the tuple (5, 12, 30, 7, 11).
def count_big(*nums):
c = 0
for n in nums:
if n > 10:
c += 1
return c
print(count_big(5, 12, 30, 7, 11))3- The values above 10 are 12, 30 and 11. Three.
- Answer: 3
Multiple Choice Question (MCQ): choose the one correct option.
What does the program print?
def add(x, l=[]):
l.append(x)
return l
add(1)
add(2)
print(add(3))[3][1, 2, 3][1, 2][]The default list is created once and shared by every call that does not pass its own list.
def add(x, l=[]):
l.append(x)
return l
add(1)
add(2)
print(add(3))[1, 2, 3]- Call 1 puts
1into the shared list, call 2 adds2, call 3 adds3. The list is[1, 2, 3]. - Option 1 is what you would expect if each call started with a fresh empty list.
- Answer: option 2.
Multiple Select Question (MSQ): select all that apply.
After the program runs, select the correct statements.
def f1(l):
l.append(9)
def f2(l):
l = l + [9]
def f3(l):
l[0] = 5
a = [1, 2]
b = [1, 2]
c = [1, 2]
f1(a)
f2(b)
f3(c)a is [1, 2, 9]b is [1, 2, 9]b is [1, 2]c is [5, 2]Changing the list the name points to is seen by the caller. Pointing the local name at a new list is not.
def f1(l):
l.append(9)
def f2(l):
l = l + [9]
def f3(l):
l[0] = 5
a = [1, 2]
b = [1, 2]
c = [1, 2]
f1(a)
f2(b)
f3(c)
print(a, b, c)[1, 2, 9] [1, 2] [5, 2]- 1 ✓
appendchanges the shared list. 2 ✗ and 3 ✓:l + [9]makes a new list bound only to the locall. 4 ✓ item assignment changes the shared list. - Answer: options 1, 3 and 4.
Too many: takes N but M given
TypeError
Optional: b
Defaults go last
Any order
After positional ones
String methods
User-defined
SyntaxError: positional after keyword
print(add) is not a call
List Functions
Minimum, maximum, joining and average of lists as functions.
Functions shine when they wrap a small, useful job. Here are five list functions (minimum, maximum, two ways of joining lists, and the average), and a sixth that uses two of them.
4.1The five functions
Find the minimum in a list
def list_min(l):
mini = l[0]
for i in range(len(l)):
if (l[i] < mini):
mini = l[i]
return miniFind the maximum in a list
def list_max(l):
maxi = l[0]
for i in range(len(l)):
if (l[i] > maxi):
maxi = l[i]
return maxiAppend a list before a given list
def list_appendBefore(l, z):
newl = []
for i in range(len(z)):
newl.append(z[i])
for i in range(len(l)):
newl.append(l[i])
return newlAppend a list after a given list
def list_appendEnd(l, z):
newl = []
for i in range(len(l)):
newl.append(l[i])
for i in range(len(z)):
newl.append(z[i])
return newlAverage of all list elements
def list_average(l):
sum = 0
for i in range(len(l)):
sum = sum + l[i]
ans = sum / len(l)
return ans- list_min / list_max. Assume the first item is the answer, then replace it whenever a smaller (or larger) item is seen. It is the same loop as Week 4, now wrapped in a function.
- list_appendBefore / list_appendEnd. Both build a new list
newlby copying two lists in a different order; the original lists are left untouched. - list_average. Add everything up in
sum, then divide bylen(l).
4.2Calling our functions
Using l = [1, 8, 4, 3, 2, 9] and z = [10, 16, 20]:
l = [1, 8, 4, 3, 2, 9]
z = [10, 16, 20]
print(list_min(l))
print(list_max(l))
print(list_appendBefore(l, z))
print(list_appendEnd(l, z))
print(list_average(l))1
9
[10, 16, 20, 1, 8, 4, 3, 2, 9]
[1, 8, 4, 3, 2, 9, 10, 16, 20]
4.5| Call | Output | Why |
|---|---|---|
| list_min(l) | 1 | the smallest item |
| list_max(l) | 9 | the largest item |
| list_appendBefore(l, z) | [10, 16, 20, 1, 8, 4, 3, 2, 9] | z first, then l |
| list_appendEnd(l, z) | [1, 8, 4, 3, 2, 9, 10, 16, 20] | l first, then z |
| list_average(l) | 4.5 | 27 / 6 |
4.3Things worth noticing (extra)
- The list is not changed. None of the five functions modifies
lorz; they only read them, and the two joining functions return a new list. sumhides a built-in. Naming a variablesumworks but shadows Python's ownsum()inside that function. A name liketotalis safer.- Empty lists break them.
l[0]on an empty list is anIndexError, and the average of an empty list divides by zero. - Built-ins do the same (extra).
min(l),max(l),sum(l) / len(l)andl + zgive the same answers. Writing the loops yourself is the exam skill.
l = [1, 8, 4, 3, 2, 9]
z = [10, 16, 20]
new = list_appendEnd(l, z)
print(l, z)
print(min(l), max(l), sum(l) / len(l), l + z == new)[1, 8, 4, 3, 2, 9] [10, 16, 20]
1 9 4.5 Trueprint(list_average([]))ZeroDivisionError: division by zero4.4Functions calling functions
Write list_range(l) that returns the largest item minus the smallest item, by using list_max and list_min.
def list_range(l):
return list_max(l) - list_min(l)
print(list_range([1, 8, 4, 3, 2, 9]))
print(list_range([5, 5, 5]))8
0A function can call other functions you have written. The big job (list_range) is now one line, because the small jobs are already solved and tested.
Write count_above_average(l) using list_average.
def count_above_average(l):
avg = list_average(l)
count = 0
for x in l:
if x > avg:
count += 1
return count
print(count_above_average([1, 8, 4, 3, 2, 9]))2The average of [1, 8, 4, 3, 2, 9] is 4.5, and the items above it are 8 and 9, so the answer is 2.
4.5Exam tips and common mistakes
- Say what each function takes and returns before writing it: "takes a list, returns a number".
- Start the answer variable from the first item (
l[0]) for min/max, and from0for sums.
- Putting
returninside the loop, which ends the function after the first item. - Printing the answer inside the function instead of returning it.
- Dividing by
len(l)without checking for an empty list.
Practice questions (graded-assignment style)
Numerical Answer Type (NAT).
What does the program print?
def list_min(l):
mini = l[0]
for i in range(len(l)):
if (l[i] < mini):
mini = l[i]
return mini
print(list_min([7, 3, 9, 2, 8]))Start with the first item and keep the smaller.
def list_min(l):
mini = l[0]
for i in range(len(l)):
if (l[i] < mini):
mini = l[i]
return mini
print(list_min([7, 3, 9, 2, 8]))2- The smallest of
[7, 3, 9, 2, 8]is2. - Answer: 2
Numerical Answer Type (NAT).
What does the program print?
def list_average(l):
total = 0
for x in l:
total = total + x
return total / len(l)
print(list_average([2, 4, 6, 8]))Total divided by the count.
def list_average(l):
total = 0
for x in l:
total = total + x
return total / len(l)
print(list_average([2, 4, 6, 8]))5.0(2 + 4 + 6 + 8) / 4 = 20 / 4 = 5.0. Division always gives a float.- Answer: 5.0
Multiple Choice Question (MCQ): choose the one correct option.
What does the program print?
def list_appendBefore(l, z):
newl = []
for i in range(len(z)):
newl.append(z[i])
for i in range(len(l)):
newl.append(l[i])
return newl
print(list_appendBefore([1, 2], [3, 4]))appendBefore(l, z) puts z first.
def list_appendBefore(l, z):
newl = []
for i in range(len(z)):
newl.append(z[i])
for i in range(len(l)):
newl.append(l[i])
return newl
print(list_appendBefore([1, 2], [3, 4]))[3, 4, 1, 2]z = [3, 4]is copied first, thenl = [1, 2].- Answer: option 2.
Multiple Choice Question (MCQ): choose the one correct option.
What does the program print?
def biggest(l):
big = l[0]
for x in l:
if x > big:
big = x
return big
print(biggest([1, 9, 5]))A return ends the function at once.
def biggest(l):
big = l[0]
for x in l:
if x > big:
big = x
return big
print(biggest([1, 9, 5]))1- The loop returns on its first pass, so only the first item,
1, is ever looked at. This is the "return inside the loop" mistake. - Answer: option 3.
Numerical Answer Type (NAT).
list_range(l) returns list_max(l) - list_min(l). What does it return for [1, 8, 4, 3, 2, 9]?
The largest is 9, the smallest is 1.
9 - 1 = 8.- Answer: 8
Multiple Select Question (MSQ): select all that apply.
Choose the correct statements about the five list functions.
An empty list makes the average divide by zero.
- Statement 3 is false: it raises a
ZeroDivisionError. - Answer: options 1, 2 and 4.
Replace when smaller or larger
return at the end
Copy two lists in order
Originals untouched
27 / 6 = 4.5
Empty list: error
sum shadows the built-in
give the same answers
Sorting and Matrix Multiplication with Functions
Two bigger programs rebuilt from small helper functions.
Two bigger programs from Week 4, rebuilt out of small functions: selection sort, and matrix multiplication. The idea: break a problem into smaller modules and solve them separately. That makes the whole problem much easier on the mind.
5.1Sorting using functions
The idea: find the smallest element in the list, append it to a new list, remove it from the original list, and repeat until the original list is empty. Finding the minimum is its own job, so it gets its own function.
def min_list(l):
mini = l[0]
for i in range(len(l)):
if (l[i] < mini):
mini = l[i]
return mini
def obvious_sort(l):
x = []
while (len(l) > 0):
mini = min_list(l)
x.append(mini)
l.remove(mini)
return xprint(obvious_sort([12, 10, 7, 18, 6, 42, 8, 5, 35, 7]))[5, 6, 7, 7, 8, 10, 12, 18, 35, 42]| Round | l before | min_list(l) returns | x after |
|---|---|---|---|
| 1 | [4, 2, 9, 2] | 2 | [2] |
| 2 | [4, 9, 2] | 2 | [2, 2] |
| 3 | [4, 9] | 4 | [2, 2, 4] |
| 4 | [9] | 9 | [2, 2, 4, 9] |
print(obvious_sort([4, 2, 9, 2]))[2, 2, 4, 9]A side effect to know about (extra)
l.remove(mini) changes the very list that was passed in. After obvious_sort(data) the caller's data is empty, because both names refer to the same list:
data = [3, 1, 2]
print(obvious_sort(data))
print(data)[1, 2, 3]
[]If the original must survive, sort a copy: write l = l[:] as the first line of obvious_sort, or call obvious_sort(data[:]).
data = [3, 1, 2]
print(obvious_sort(data[:]))
print(data)[1, 2, 3]
[3, 1, 2]5.2Matrix multiplication using functions
The same matrix multiplication as in Week 4, broken into small, reusable helper functions: one to build a zero matrix, one for a dot product, one to pull out a row, one to pull out a column, and one that ties them all together.
def initialize_mat(dim):
C = []
for i in range(dim):
C.append([])
for i in range(dim):
for j in range(dim):
C[i].append(0)
return C
def dot_product(u, v):
dim = len(u)
ans = 0
for i in range(dim):
ans = ans + (u[i] * v[i])
return ans
def row(M, i):
dim = len(M)
l = []
for k in range(dim):
l.append(M[i][k])
return l
def column(M, j):
dim = len(M)
l = []
for k in range(dim):
l.append(M[k][j])
return l
def mat_mul(A, B):
dim = len(A)
C = initialize_mat(dim)
for i in range(dim):
for j in range(dim):
C[i][j] = dot_product(row(A, i), column(B, j))
return C| Function | Job | Takes | Returns |
|---|---|---|---|
| initialize_mat(dim) | builds a dim × dim matrix of zeros | dim | a list of lists |
| dot_product(u, v) | sum of u[i] * v[i] | two lists | a number |
| row(M, i) | the i-th row of M | M, i | a list |
| column(M, j) | the j-th column of M | M, j | a list |
| mat_mul(A, B) | ties the helpers together | A, B | the product matrix |
Each helper does exactly one job: row() and column() pull out a slice, dot_product() combines two slices, and mat_mul() just wires them together. This is the same triple-nested-loop logic from Week 4, split into readable pieces.
A = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
B = [[1, 2, 1], [6, 2, 3], [4, 2, 1]]
print(mat_mul(A, B))[[25, 12, 10], [58, 30, 25], [91, 48, 40]]The answer is the same as the loop version of Week 4. Each helper can also be tried on its own (extra):
A = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
B = [[1, 2, 1], [6, 2, 3], [4, 2, 1]]
print(row(A, 1))
print(column(B, 0))
print(dot_product(row(A, 1), column(B, 0)))
print(initialize_mat(2))[4, 5, 6]
[1, 6, 4]
58
[[0, 0], [0, 0]]row, column and mat_mul take dim = len(M) and use it for both the rows and the columns, so they work for square matrices (same number of rows and columns) only.5.3Going deeper
- Why split? (extra) Each piece can be tested alone, reused elsewhere, and replaced with a better version (for example a faster
min_list) without touching the rest. - Cost (extra). The function version does the same number of multiplications as the loop version; the benefit is clarity, not speed.
5.4Exam tips and common mistakes
- Draw the call picture: who calls whom, what is passed, what comes back.
- Trace the sorting by hand with two columns: what is left in
l, what has gone intox.
- Expecting the list you passed to
obvious_sortto still be there afterwards. - Mixing up
row(B, j)andcolumn(B, j)inmat_mul. - Forgetting
returnin a helper, somat_mulworks withNone.
Practice questions (graded-assignment style)
Multiple Choice Question (MCQ): choose the one correct option.
What does the program print?
def min_list(l):
mini = l[0]
for v in l:
if v < mini:
mini = v
return mini
def obvious_sort(l):
x = []
while len(l) > 0:
m = min_list(l)
x.append(m)
l.remove(m)
return x
data = [3, 1, 2]
print(obvious_sort(data))
print(data)l.remove empties the passed list.
def min_list(l):
mini = l[0]
for v in l:
if v < mini:
mini = v
return mini
def obvious_sort(l):
x = []
while len(l) > 0:
m = min_list(l)
x.append(m)
l.remove(m)
return x
data = [3, 1, 2]
print(obvious_sort(data))
print(data)[1, 2, 3]
[]- The function returns the sorted list
[1, 2, 3], and the originaldatahas been emptied. - Answer: option 3.
Numerical Answer Type (NAT).
What does the program print?
def dot_product(u, v):
ans = 0
for i in range(len(u)):
ans = ans + (u[i] * v[i])
return ans
print(dot_product([1, 2, 3], [4, 5, 6]))Multiply matching items and add.
def dot_product(u, v):
ans = 0
for i in range(len(u)):
ans = ans + (u[i] * v[i])
return ans
print(dot_product([1, 2, 3], [4, 5, 6]))321·4 + 2·5 + 3·6 = 32.- Answer: 32
Multiple Choice Question (MCQ): choose the one correct option.
What does the program print?
def column(M, j):
l = []
for k in range(len(M)):
l.append(M[k][j])
return l
print(column([[1, 2, 3], [4, 5, 6], [7, 8, 9]], 1))column(M, j) takes item j of every row.
def column(M, j):
l = []
for k in range(len(M)):
l.append(M[k][j])
return l
print(column([[1, 2, 3], [4, 5, 6], [7, 8, 9]], 1))[2, 5, 8]- Column index 1 is the second column:
2, 5, 8. - Answer: option 4.
Numerical Answer Type (NAT).
What is the value printed?
A = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
B = [[1, 2, 1], [6, 2, 3], [4, 2, 1]]
print(sum(A[2][k] * B[k][1] for k in range(3)))Row 2 of A against column 1 of B.
A = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
B = [[1, 2, 1], [6, 2, 3], [4, 2, 1]]
print(sum(A[2][k] * B[k][1] for k in range(3)))487·2 + 8·2 + 9·2 = 48.- Answer: 48
Multiple Choice Question (MCQ): choose the one correct option.
How can obvious_sort(data) be changed to leave data unchanged?
Sort a copy of the list.
data[:]is a separate list, so theremovecalls empty the copy, notdata.- Answer: option 1.
Multiple Select Question (MSQ): select all that apply.
Choose the correct statements.
The number of multiplications is unchanged.
- Statement 3 is false: the work is the same, only the structure is clearer.
- Answer: options 1, 2 and 4.
obvious_sort moves it to x
l.remove empties l
row, column
mat_mul ties them
same as Week 4
Solve the pieces
Wire them together
Square matrices only
Recursion
Functions that call themselves: factorial, Fibonacci, base conditions and limits.
Recursion is the process of defining something in terms of itself. A physical-world example is two parallel mirrors facing each other: any object placed between them is reflected again and again.
6.1What is recursion?
In Python, a function can call other functions. It is even possible for a function to call itself. These constructs are termed recursive functions.
def recurse():
# ...
recurse()
# ...
recurse()6.2Example: factorial
The factorial of n is n(n-1)(n-2)...3*2*1. It is defined in terms of a smaller factorial: n! = n × (n-1)!.
def factorial(x):
if x == 1:
return 1
else:
return (x * factorial(x - 1))
num = 3
print("The factorial of", num, "is", factorial(num))The factorial of 3 is 6factorial() is a recursive function as it calls itself. When called with a positive integer, it recursively calls itself with a smaller number. Each call multiplies the number with the factorial of the number below it, until it is equal to 1. The steps:
factorial(3) # 1st call with 3
3 * factorial(2) # 2nd call with 2
3 * 2 * factorial(1) # 3rd call with 1
3 * 2 * 1 # returns from 3rd call as 1
3 * 2 # returns from 2nd call
6 # returns from 1st callOur recursion ends when the number reduces to 1. This is called the base condition. Every recursive function must have a base condition that stops the recursion, or else the function calls itself infinitely.
The depth limit. The Python interpreter limits the depth of recursion to help avoid infinite recursion, which would result in a stack overflow. By default the maximum depth of recursion is 1000. If the limit is crossed it results in a RecursionError.
import sys
print(sys.getrecursionlimit())
def f(n):
return f(n + 1)
f(0)1000
RecursionError: maximum recursion depth exceededx == 1, so factorial(0) (and any negative number) never reaches it: the calls go 0, -1, -2, ... forever until the limit is crossed. A safer base condition is x <= 1, which also gives the correct 0! = 1.def factorial(x):
if x == 1:
return 1
else:
return (x * factorial(x - 1))
print(factorial(0))RecursionError: maximum recursion depth exceededdef factorial(x):
if x <= 1:
return 1
return x * factorial(x - 1)
print(factorial(0), factorial(1), factorial(5))1 1 1206.3More recursive examples
Sum of the first n numbers
def sum_n(n):
if n == 0:
return 0
return n + sum_n(n - 1)
print(sum_n(10))55The smaller problem is sum_n(n - 1); the base case is sum_n(0) = 0. The chain: sum_n(3) = 3 + sum_n(2) = 3 + 2 + sum_n(1) = 3 + 2 + 1 + sum_n(0) = 6.
Fibonacci numbers
def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
print(fib(10))
print([fib(i) for i in range(10)])55
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]Each Fibonacci number is the sum of the two before it: fib(n) = fib(n - 1) + fib(n - 2), with the two base cases fib(0) = 0 and fib(1) = 1 (the test n <= 1 covers both). Here the function calls itself twice.
Counting the calls (extra). Because the same values are recomputed, the number of calls grows very fast:
calls = 0
def fib(n):
global calls
calls += 1
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
fib(4)
print(calls)
calls = 0
fib(10)
print(calls)
calls = 0
fib(20)
print(calls)9
177
218916.4Recursion or a loop?
def fact_loop(n):
result = 1
for i in range(2, n + 1):
result = result * i
return result
def fact_rec(n):
if n <= 1:
return 1
return n * fact_rec(n - 1)
print(fact_loop(5), fact_rec(5))120 120Both give the same answer. The loop is usually faster and uses less memory; the recursive version reads like the definition.
6.5Advantages and disadvantages of recursion
- Recursive functions make the code look clean and elegant.
- A complex task can be broken down into simpler sub-problems using recursion.
- Sequence generation is easier with recursion than with some nested iteration.
- Sometimes the logic behind recursion is hard to follow through.
- Recursive calls are expensive (inefficient) as they take up a lot of memory and time.
- Recursive functions are hard to debug.
6.6Going deeper
- The recipe (extra). To write a recursive function: (1) find the base case that can be answered directly, (2) write the step that reduces the problem, (3) check that every call moves closer to the base case.
- Why the depth limit helps (extra). Each unfinished call waits in memory. Without a limit an endless recursion would eat all the memory;
RecursionErroris Python stopping it safely.
6.7More recursion: gcd, power, digit sum, Fibonacci and the Tower of Hanoi
Every recursive function has the same two parts: a base case that answers directly, and a recursive step that reduces the problem to a smaller one of the same kind.
A. Four small recursive functions
def power(x, n):
if n == 0: # base case: x to the power 0 is 1
return 1
return x * power(x, n - 1)
def digit_sum(n):
if n < 10: # base case: one digit
return n
return n % 10 + digit_sum(n // 10)
def gcd(a, b):
if b == 0: # base case: gcd(a, 0) is a
return a
return gcd(b, a % b) # Euclid's step
def count_down_sum(n):
if n == 0:
return 0
return n + count_down_sum(n - 1)
print(power(2, 10))
print(digit_sum(9045))
print(gcd(252, 105))
print(count_down_sum(3))1024
18
21
6| Function | Base case | Recursive step |
|---|---|---|
power(x, n) | : 1 | |
digit_sum(n) | : | last digit + sum of the rest (n % 10, n // 10) |
gcd(a, b) | : |
Trace of digit_sum(9045): , then , then , and . Back up the stack: , , .
B. Fibonacci by recursion repeats work
, with and , is a natural recursion. But each call makes two more, and the same values are recomputed many times. Counting the calls:
calls = 0
def fib(n):
global calls
calls += 1
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
for n in (4, 5, 6):
calls = 0
value = fib(n)
print("fib(" + str(n) + ") =", value, "in", calls, "calls")fib(4) = 3 in 9 calls
fib(5) = 5 in 15 calls
fib(6) = 8 in 25 callsThe number of calls grows roughly like the Fibonacci numbers themselves: a loop that keeps the last two values does the same job with only steps. This is the main disadvantage of recursion to mention in an answer: repeated work and extra memory for each pending call.
C. The Tower of Hanoi
Move disks from peg A to peg C using peg B, never putting a larger disk on a smaller one. The recursion says: move disks to B, move the biggest disk to C, then move the disks from B onto C. So the number of moves satisfies , , giving .
def hanoi(n, a, b, c, moves):
if n == 0:
return
hanoi(n - 1, a, c, b, moves) # clear the top n-1 disks onto b
moves.append((a, c)) # move the largest disk
hanoi(n - 1, b, a, c, moves) # bring the n-1 disks onto c
for n in (1, 2, 3, 4):
moves = []
hanoi(n, "A", "B", "C", moves)
print(n, len(moves))1 1
2 3
3 7
4 15D. No base case, no end
If the recursion never reaches a base case, the calls pile up until Python stops them with a RecursionError ("maximum recursion depth exceeded"):
def forever(n):
return forever(n + 1)
forever(0)RecursionError: maximum recursion depth exceededn, shorter string)? (3) Is the returned value of the recursive call actually used (return f(n - 1), not just f(n - 1))?6.8Exam tips and common mistakes
- Trace a recursive call by writing the chain down (
3 * factorial(2), ...) and then the answers back up. - First name the base condition: if you cannot find one, the function never stops.
- Forgetting the base condition (infinite recursion,
RecursionError). - A recursive step that does not move toward the base case.
- Forgetting the
returnon the recursive call, which hands backNone.
Practice questions (graded-assignment style)
Numerical Answer Type (NAT).
What does the program print?
def factorial(x):
if x == 1:
return 1
return x * factorial(x - 1)
print(factorial(4))4 * 3 * 2 * 1.
def factorial(x):
if x == 1:
return 1
return x * factorial(x - 1)
print(factorial(4))24factorial(4) = 4 × factorial(3)= 4 × 6 = 24.- Answer: 24
Numerical Answer Type (NAT).
What does the program print?
def sum_n(n):
if n == 0:
return 0
return n + sum_n(n - 1)
print(sum_n(5))5 + 4 + 3 + 2 + 1 + 0.
def sum_n(n):
if n == 0:
return 0
return n + sum_n(n - 1)
print(sum_n(5))15sum_n(5) = 5+4+3+2+1+0= 15.- Answer: 15
Numerical Answer Type (NAT).
What does the program print?
def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
print(fib(6))The sequence is 0, 1, 1, 2, 3, 5, 8.
def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
print(fib(6))8fib(6)is the 7th number of 0, 1, 1, 2, 3, 5, 8, which is 8.- Answer: 8
Multiple Choice Question (MCQ): choose the one correct option.
What happens when the program runs?
def f(n):
return f(n + 1)
print(f(0))Nothing stops the calls.
def f(n):
return f(n + 1)
print(f(0))RecursionError: maximum recursion depth exceeded- The function always calls itself and never stops, so Python raises a
RecursionError. - Answer: option 3.
Numerical Answer Type (NAT).
How many calls of fib are made in all when fib(4) is evaluated, counting the first call? (See the call tree.)
Count the boxes of the call tree.
- The tree has 1 + 2 + 4 + 2 = 9 boxes:
fib(4);fib(3),fib(2);fib(2),fib(1),fib(1),fib(0);fib(1),fib(0). - Answer: 9
Multiple Select Question (MSQ): select all that apply.
Choose the correct statements.
Recursive calls cost memory and time.
- Statement 3 is false: recursive calls are usually slower than a loop.
- Answer: options 1, 2 and 4.
Numerical Answer Type (NAT).
What does the program print?
def f(n):
if n == 0:
return 0
return n % 10 + f(n // 10)
print(f(4073))Each call adds the last digit and recurses on the number without it, until the number becomes 0.
def f(n):
if n == 0:
return 0
return n % 10 + f(n // 10)
print(f(4073))14- , and . Total .
- Answer: 14
Numerical Answer Type (NAT).
What does the program print?
def gcd(a, b):
if b == 0:
return a
return gcd(b, a % b)
print(gcd(48, 18))Follow the pair: until the second number is 0.
def gcd(a, b):
if b == 0:
return a
return gcd(b, a % b)
print(gcd(48, 18))6- . The answer is the first number when the second is 0: .
- Answer: 6
Numerical Answer Type (NAT).
The function fib(n) returns n if n < 2 and fib(n - 1) + fib(n - 2) otherwise. Counting the first call, how many times is fib called in total when we run fib(5)?
Let be the number of calls. Then and .
- , , , .
- Running the code with a call counter confirms it (the call counts for are ).
- Answer: 15
Multiple Choice Question (MCQ): choose the one correct option.
What happens when countdown(3) is called?
def countdown(n):
print(n)
countdown(n - 1)3 2 1 and stops.3 2 1 0 and stops.RecursionError stops it.3.There is no base case, so nothing makes the recursion stop.
- The numbers go with no end, until Python raises
RecursionError: maximum recursion depth exceeded. - Adding
if n == 0: returnat the top (a base case) would fix it. - Answer: option 3.
Numerical Answer Type (NAT).
What is the minimum number of moves to transfer 5 disks in the Tower of Hanoi?
, so .
- , , , , (and ).
- Answer: 31
Multiple Select Question (MSQ): select all that apply.
Choose the correct statements.
Recall the advantages and disadvantages of recursion.
- 1 ✓ without a base case the calls never stop. 2 ✓ any recursion can be replaced by a loop (sometimes with an explicit stack). 3 ✗ it recomputes the same values many times. 4 ✓ calls stack up in memory.
- Answer: options 1, 2 and 4.
Smaller problem each time
Mirrors facing each other
Recursive step shrinks it
Calls go down, answers up
RecursionError beyond
sum_n(10) = 55
fib(10) = 55
fib(20) makes 21891 calls
Hard to debug
Try It Yourself
The ten tasks of the source, each with a worked and executed solution.
Ten tasks from the source, to be done without looking back at the examples. Each has a worked, really executed solution. Try the task first, then compare.
7.1Tasks 1 to 5: functions, return, scope, global, defaults
Write a function that takes a temperature in Celsius and returns it in Fahrenheit.
def c_to_f(c):
return (c * 9/5) + 32
print(c_to_f(100))
print(c_to_f(0))
print(c_to_f(37))212.0
32.0
98.6100 °C is 212.0 °F, freezing point 0 °C is 32.0 °F, and normal body temperature 37 °C is 98.6 °F (extra).
Write a function that prints "Hi" instead of returning it, then explain what print(function()) shows.
def say_hi():
print("Hi")
print(say_hi())Hi
NoneIt prints "Hi", then None: say_hi() has no return statement.
Predict the output: a variable x = 5 outside a function, and the function sets a local x = 100 and prints it, then print x again outside.
x = 5
def show():
x = 100
print("Inside:", x)
show()
print("Outside:", x)Inside: 100
Outside: 5Write a function that increments a global counter variable by 1 every time it is called.
counter = 0
def increment():
global counter
counter += 1
return counter
print(increment())
print(increment())1
2Write greet(name, greeting="Hello") and call it both with and without the second argument.
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
print(greet("Asha"))
print(greet("Asha", "Welcome"))Hello, Asha!
Welcome, Asha!7.2Tasks 6 to 10: keywords, lists, recursion, primes
Write a function that builds a full address from city, state and pin, and call it using keyword arguments in a different order.
def address(city, state, pin):
return f"{city}, {state} - {pin}"
print(address(pin="600001", city="Chennai", state="TN"))Chennai, TN - 600001Write a function that takes a list and returns the sum of all even numbers in it.
def sum_evens(l):
total = 0
for n in l:
if n % 2 == 0:
total += n
return total
print(sum_evens([1, 2, 3, 4, 5, 6]))122 + 4 + 6 = 12.
Write a recursive function to find the sum of the first n natural numbers.
def sum_n(n):
if n == 0:
return 0
return n + sum_n(n - 1)
print(sum_n(10))55Write a recursive function to compute the nth Fibonacci number.
def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
print(fib(10))55Write a function that checks whether a number is prime, then use it to print all primes below 50.
def is_prime(n):
if n < 2:
return False
for i in range(2, n):
if n % i == 0:
return False
return True
for num in range(50):
if is_prime(num):
print(num, end=' ')2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 There is a space after the last number because end=' ' puts one after every print. The function returns False as soon as it finds a divisor, so it does not scan the rest. Only is_prime decides; the loop just asks it for each number from 0 to 49.
def is_prime(n):
if n < 2:
return False
i = 2
while i * i <= n:
if n % i == 0:
return False
i += 1
return True
print([k for k in range(50) if is_prime(k)])[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]The second version (extra) stops testing at the square root of n (i * i <= n) and gives the same primes with fewer steps.
7.3Going deeper
- Predict first. For each task write the expected output before running. A mismatch means a misunderstanding of return, scope or the order of the calls.
- Variations (extra). Task 1 in the reverse direction; Task 7 for odd numbers; Task 8 with a recursion for the product; Task 10 returning the list of primes instead of printing them.
7.4Exam tips and common mistakes
- Read what each function must do: print something, or return something. Many marks hide in that difference.
- Test the function on a tiny input you can do by hand.
- Printing the result inside the function when the task says "return".
- Forgetting the base case in a recursive task.
- Using
globalfor something that a parameter and a return would handle better.
Practice questions (graded-assignment style)
Numerical Answer Type (NAT).
What does the program print?
def c_to_f(c):
return (c * 9/5) + 32
print(c_to_f(100))100 * 9/5 + 32.
def c_to_f(c):
return (c * 9/5) + 32
print(c_to_f(100))212.0100 × 9/5 = 180.0, and180.0 + 32 = 212.0.- Answer: 212.0
Multiple Choice Question (MCQ): choose the one correct option.
What does the program print?
def say_hi():
print("Hi")
print(say_hi())The function prints and returns nothing.
def say_hi():
print("Hi")
print(say_hi())Hi
None- "Hi" is printed inside the function; then
printshows the returned value, which isNone. - Answer: option 3.
Numerical Answer Type (NAT).
What does the program print?
def sum_evens(l):
total = 0
for n in l:
if n % 2 == 0:
total += n
return total
print(sum_evens([2, 3, 4, 7, 12]))Add only the even numbers.
def sum_evens(l):
total = 0
for n in l:
if n % 2 == 0:
total += n
return total
print(sum_evens([2, 3, 4, 7, 12]))18- The even numbers are 2, 4, 12:
2 + 4 + 12 = 18. - Answer: 18
Numerical Answer Type (NAT).
How many primes does the program print?
def is_prime(n):
if n < 2:
return False
for i in range(2, n):
if n % i == 0:
return False
return True
count = 0
for num in range(50):
if is_prime(num):
count += 1
print(count)There are 15 primes below 50.
def is_prime(n):
if n < 2:
return False
for i in range(2, n):
if n % i == 0:
return False
return True
count = 0
for num in range(50):
if is_prime(num):
count += 1
print(count)15- The primes below 50 are 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47: fifteen of them.
- Answer: 15
Multiple Choice Question (MCQ): choose the one correct option.
What does the program print?
def address(city, state, pin):
return f"{city}, {state} - {pin}"
print(address(pin="600001", city="Chennai", state="TN"))Keywords match names, not positions.
def address(city, state, pin):
return f"{city}, {state} - {pin}"
print(address(pin="600001", city="Chennai", state="TN"))Chennai, TN - 600001- The names decide the values, so the order of the call does not matter.
- Answer: option 2.
Multiple Select Question (MSQ): select all that apply.
Choose the correct statements.
A divisor means the number is not prime.
- Statement 4 is false: finding a divisor makes
is_primereturnFalse. - Answer: options 1, 2 and 3.
Week 5 Summary Sheet
One page to revise all of Week 5: the sheet, the errors, the checklist.
Everything from Week 5 on one page: the revision sheet, the chart of arguments and scope, and the usual mistakes.
8.1Revision sheet
| Topic | Key point |
|---|---|
| def name(params): | Defines a function |
| return | Exits a function, optionally sending back a value |
| No return | Function returns None by default |
| Local scope | Variables inside a function are not visible outside it |
| Global scope | Outer variables are readable, but not writable, inside a function |
| global x | Lets a function modify an outer variable |
| Default argument | def f(a, b=10):, b is optional |
| Rule | Non-default arguments cannot follow default ones |
| Keyword argument | f(b=2, a=4): order does not matter |
| Rule | Keyword arguments must follow positional ones |
| Function types | In-built, library, string methods, user-defined |
| Recursion | A function calling itself |
| Base condition | Stops the recursion from running forever |
| Max recursion depth | 1000 by default; crossing it gives a RecursionError |
8.2Errors you can now read
| What went wrong | The error |
|---|---|
| too few arguments | TypeError: add() missing 1 required positional argument: 'b' |
| too many arguments | TypeError: add() takes 2 positional arguments but 3 were given |
| a default before a non-default | SyntaxError: parameter without a default follows parameter with a default |
| positional after keyword | SyntaxError: positional argument follows keyword argument |
| assigning to an outer name without global | UnboundLocalError: cannot access local variable 'x' where it is not associated with a value |
| recursion with no base case | RecursionError: maximum recursion depth exceeded |
8.3Last-minute checklist
- Function syntax:
def, parameters, colon, body - Calling a function with the right number of arguments
returnvs a function with no return (returnsNone)- Local scope vs global scope
- The
globalkeyword and when it is needed - Default arguments and their ordering rule
- Keyword arguments and their ordering rule
- Mixing positional and keyword arguments correctly
- In-built vs library vs user-defined functions
- Writing small helper functions (min, max, average of a list)
- Sorting a list using a helper "find minimum" function
- Recursion: a function calling itself
- Identifying the base condition in a recursive function
- Factorial and Fibonacci as classic recursion examples
8.4Common errors to avoid
| Mistake | Correct idea |
|---|---|
| print(sums) | Shows the function object. Call it: print(sums(5, 6)). |
| using print and expecting a value back | print shows, return gives back. |
| assigning to an outside variable | Makes a new local, unless the name is declared global. |
| def f(a=1, b): | Non-default after default is a SyntaxError. |
| f(a=1, 2) | Positional after keyword is a SyntaxError. |
| recursion without a base case | Never stops: RecursionError. |
| factorial(0) with x == 1 | Never reaches the base case. Use x <= 1. |
return or None
Calls jump in and back
Global outside, read only
global x to modify
Keyword: after positional
Count must match
obvious_sort with min_list
mat_mul from helpers
factorial, sum_n, fib
Depth limit 1000
Missing base case
Local vs global confusion