Functions
A function is a named, tested, reusable piece of behaviour with a written contract. In this course the contract — the type annotation and the docstring — is graded as heavily as the code, because it is what makes the code usable by anyone else.
- Write a function with type annotations and a docstring containing examples.
- Explain the difference between a parameter and an argument.
- Predict what a function returns, including when it returns
None. - Say why a local variable is invisible outside its function.
Without functions, a program is one long script where every change risks breaking something unrelated. Functions draw boundaries: inside is an implementation you can change freely, outside is a contract others rely on.
Every library you will ever import is a set of functions whose docstrings are the only thing you read. Writing yours properly is practice for the job, and in this course it is directly worth marks.
The design-recipe approach used here — contract, examples, then body — comes from the How to Design Programs tradition, which UofT's intro sequence has drawn on for years. The claim is that most beginner difficulty is not syntax but not knowing what to write next.
Anatomy of a function
def area(width: float, height: float) -> float:
"""Return the area of a rectangle with the given width and height.
>>> area(3.0, 4.0)
12.0
>>> area(0.0, 5.0)
0.0
"""
return width * heightdef names the function; the parentheses hold
the parameters.: float and
-> float state the type contract. Python does not enforce
them, but the course requires them and they document intent.doctest format —
runnable, as Unit 8 shows.A parameter is the name in the definition; an argument
is the value supplied at the call. In area(3.0, 4.0),
width is a parameter and 3.0 is an
argument.
A function with no return returns None. So
x = print('hi') sets x to None:
print displays a value, it does not produce one. Confusing
printing with returning is the most common beginner error in this
course.
# wrong: caller gets None and cannot use the result
def double_bad(n: int) -> None:
print(n * 2)
# right: caller decides whether to print, store, or reuse
def double(n: int) -> int:
"""Return twice n."""
return n * 2
print(double(5) + 1) # 11 - only possible with the returning versionScope
Names bound inside a function exist only while it runs. They are not visible outside, and they do not clash with names elsewhere. Assigning to a name inside a function always creates or updates a local name, even if a global of the same name exists.
total = 100
def add_up(xs: list) -> int:
"""Return the sum of xs."""
total = 0 # a NEW local, unrelated to the global
for x in xs:
total = total + x
return total
print(add_up([1, 2, 3])) # 6
print(total) # 100 - untouchedThis is a feature. It means you can write a function without auditing the whole program for name collisions.
Reading a global from inside a function works, and it is still a bad habit: the function's behaviour then depends on something not in its parameter list, so the contract is a lie. Pass what you need as an argument and return what you produce.
Designing a function
Writing the examples first is not ceremony. It forces the ambiguous decisions — what does this do with an empty list? — before the code has hidden them.
def average(nums: list) -> float:
"""Return the mean of nums, or 0.0 if nums is empty.
>>> average([1, 2, 3])
2.0
>>> average([])
0.0
"""
if not nums:
return 0.0
return sum(nums) / len(nums)Without the second example, the empty case is a
ZeroDivisionError discovered by whoever calls it. Writing the
example forced a decision and documented it.
• Printing instead of returning. The caller cannot do anything with
what was printed.
• Forgetting that code after return never runs.
• Writing a docstring that describes the implementation rather than the
result. Say what it returns, not how it loops.
• Mutable default arguments: def f(xs=[]) reuses the
same list on every call. Use None as the default and
create the list inside.
Practice
Predict each answer before running it. Original exercises written for this guide in the style of the course's weekly sets.
What does x hold?
def greet(name: str) -> None:
print('Hi ' + name)
x = greet('Sam')Answer
None. The
function prints but does not return, so the call evaluates to
None. "Hi Sam" appears on screen but is not
captured.What does this return for [1, 2, 3]?
def f(xs: list) -> int:
for x in xs:
return x
return 0Answer
1. The
return fires on the first iteration, so the loop never
continues. This is a common accident when a loop was meant to accumulate
something.Does this print 5 or 10?
n = 5
def bump() -> None:
n = 10
bump()
print(n)Answer
5. The
assignment inside bump creates a local n that
disappears when the call ends.Write count_above(nums, k) returning how many items exceed
k.
Solution
def count_above(nums: list, k: float) -> int:
"""Return how many values in nums are strictly greater than k.
>>> count_above([1, 5, 9], 4)
2
>>> count_above([], 0)
0
"""
total = 0
for x in nums:
if x > k:
total = total + 1
return totalWhy does this accumulate across calls?
def add(item, bag=[]):
bag.append(item)
return bagAnswer
def executes, and reused by every call
that omits bag. Use bag=None and
if bag is None: bag = [] inside.Improve: """Loops through the list and adds."""
Answer
"""Return the sum
of nums.""" — states the result, not the mechanism. The caller does
not care that a loop was involved.Return both the minimum and the maximum of a non-empty list.
Solution
def min_max(nums: list) -> tuple:
"""Return (smallest, largest) of non-empty nums.
>>> min_max([3, 1, 4])
(1, 4)
"""
return (min(nums), max(nums))lo, hi = min_max(xs).Using double and area from this page, what is
area(double(2), 3)?
Answer
12. The
inner call is evaluated first, giving 4, then
area(4, 3). Functions compose because they
return.Expanded topic checklist
Course coverage. This unit is part of the complete CSC108H1 topic map. It should cover the following items before you move on:
- Definition and call. Parameters versus arguments, annotations, return.
- Docstrings. One-sentence summary plus doctest examples including an edge case.
- Scope. Local names, and why globals are a bad habit.
- Design order. Contract and examples before body.
The calendar lists functions under "program structure". Docstring and annotation style is set by the course handout each term; follow that exactly, since it is mechanically checked on assignments.
A function packages behaviour behind a contract. Contracts make testing possible (Unit 8) and make the object methods of Unit 7 comprehensible, since a method is just a function that belongs to a type.
Deep practice extensions
Use these after the main practice set. They push on definitions and edge cases rather than routine calculation.
What does a function with no return give back?
Guide
None.
Always — there is no such thing as returning nothing.Distinguish them.
Guide
def; argument is the value passed at the
call.What does writing docstring examples before the body buy you?
Guide
What happens when you assign to a name inside a function?