Functions and Structure
Writing your own functions, parameters and return values, scope, default arguments, and structuring a program that stays readable at 300 lines.
Defining and calling
def greet(name):
print(f"Hello, {name}!")
greet("Aisha")
greet("Ben")defstarts the definitionnameis a parameter, a value the function needs"Aisha"is an argument, the actual value passed in
Returning a value
def area_of_rectangle(width, height):
return width * height
a = area_of_rectangle(5, 3)
print(a) # 15
print(area_of_rectangle(2, 8)) # 16A function that returns gives a value back so it can be used in a calculation, stored, or printed. A function that only prints has thrown the answer away.
print shows something to the user. return hands a value back to the program. A function that calculates something should almost always return it.
Multiple return values
def stats(numbers):
return min(numbers), max(numbers), sum(numbers) / len(numbers)
lowest, highest, mean = stats([4, 8, 15, 16, 23])
print(lowest, highest, mean)Default and named arguments
def greet(name, greeting="Hello"):
print(f"{greeting}, {name}!")
greet("Aisha") # Hello, Aisha!
greet("Ben", "Good morning") # Good morning, Ben!
greet(greeting="Hi", name="Chloe") # Hi, Chloe!- Define it once with def, naming the parameters it will be given.
- Call it with arguments. They fill the parameters, w = 3 and h = 4.
- return hands the result back to the line that called it.
- A procedure does a job. A function also gives a value back.
Scope
A local variable exists only inside the function that created it.
def calculate():
result = 42 # local
return result
calculate()
print(result) # NameError, result does not exist out hereA global variable exists everywhere.
TAX_RATE = 0.2 # global constant
def add_tax(amount):
return amount * (1 + TAX_RATE)Reading a global is fine. Changing one from inside a function requires the global keyword and is almost always a sign the design could be better.
score = 0
def add_point():
global score # avoid this where you can
score += 1Better:
def add_point(score):
return score + 1
score = add_point(score)Why local is better
- Nothing outside the function can change it by accident
- Two functions can use the same name without interfering
- The function's behaviour depends only on what you pass in, so it is far easier to test
- The program is the house. A variable made in the hallway is global, and any function can read it.
- Each function is a room. Variables made inside it are local and live only in that room.
- Outside code cannot see a room's local variables: asking for one gives a NameError.
Structuring a program
One function, one job
def load_scores(filename):
...
def calculate_average(scores):
...
def find_top_student(scores):
...
def display_report(scores):
...
def main():
scores = load_scores("scores.txt")
display_report(scores)
main()The main() function at the bottom reads like a summary of the program. That is the goal.
A complete example
def get_valid_number(prompt, low, high):
# Keep asking until the answer is a whole number in range
while True:
entry = input(prompt).strip()
if entry.lstrip("-").isdigit():
value = int(entry)
if low <= value <= high:
return value
print(f"Please enter a whole number between {low} and {high}.")
def grade_for(mark):
if mark >= 70:
return "Distinction"
elif mark >= 50:
return "Merit"
elif mark >= 40:
return "Pass"
return "Fail"
def summarise(marks):
return {
"count": len(marks),
"mean": sum(marks) / len(marks),
"highest": max(marks),
"lowest": min(marks),
}
def main():
marks = []
how_many = get_valid_number("How many marks? ", 1, 30)
for i in range(how_many):
mark = get_valid_number(f"Mark {i + 1}: ", 0, 100)
marks.append(mark)
stats = summarise(marks)
print(f"\n{stats['count']} marks entered")
print(f"Mean: {stats['mean']:.1f} ({grade_for(stats['mean'])})")
print(f"Highest: {stats['highest']} ({grade_for(stats['highest'])})")
print(f"Lowest: {stats['lowest']} ({grade_for(stats['lowest'])})")
main()Every function does one thing, is named after what it does, and can be tested on its own. That is what makes a program maintainable.
Docstrings
def area_of_circle(radius):
'''Return the area of a circle with the given radius.'''
return 3.14159 * radius ** 2
help(area_of_circle)Key terms you must be able to define
- Function
- A named block of code that performs a task and can be called from anywhere.
- Parameter
- A variable in a function definition that receives a value when it is called.
- Argument
- The actual value passed to a function when it is called.
- return
- A statement that sends a value back to the code that called the function.
- Local variable
- A variable that exists only within the function where it was created.
- Global variable
- A variable accessible from anywhere in the program.
- Scope
- The region of a program in which a variable can be accessed.
- Default argument
- A parameter given a value in the definition, used when no argument is supplied.
- Docstring
- A string at the top of a function describing what it does.
Flashcards
Say the definition out loud before you flip the card. Retrieving it from memory is what builds recall, reading it again is not.
Reach the top grade on this topic
- Write functions that return rather than print, so results can be reused
- Give every function one job and a name that says exactly what that job is
- Keep variables local, and pass values in as parameters instead of using globals
- Write a main() function that reads like a summary of the whole program
- Add a docstring to any function whose purpose is not obvious from its name
Mistakes that cost marks here
- Printing inside a function that should return, so the value cannot be reused.
- Forgetting return entirely, so the function silently gives back None.
- Using global variables to pass values around instead of parameters.
- Writing one function that does five things, which then cannot be tested or reused.
- Calling a function before defining it.
A function that does one thing and has a name that says what it does is the single most powerful tool in programming. Almost every difficult codebase in the world got that way because someone stopped doing this.
Knowledge check
def greet(name, greeting="Hello"), what is greeting?return min(x), max(x) do?Exam-style questions
Five written questions in the style of the real paper. Write a full answer, then let the marker check it against the mark scheme.
- Lprint displays a value to the user
- Lreturn sends a value back to the code that called the function
- LA returned value can be stored or used again, a printed value cannot
The print function displays a value on the screen for the user to read, and once it has been displayed the program has no further access to it. The return statement sends a value back to whatever code called the function, so it can be stored in a variable, passed into another function or used in a further calculation. A function that calculates something should almost always return it, because a function that only prints has effectively thrown the result away and cannot be used as a building block anywhere else in the program.
- LUses def with a suitable name and a parameter
- LApplies the subtraction of 32 first
- LMultiplies by 5 and divides by 9
- LReturns the result rather than printing it
def convert_to_celsius(fahrenheit): return (fahrenheit - 32) * 5 / 9 print(convert_to_celsius(212)) # 100.0 The brackets around the subtraction are essential, because without them Python would multiply by 5 before subtracting 32 and give the wrong answer. The function returns the value rather than printing it, so the result can be stored, rounded, or used in a further calculation by whatever called it.
- LScope is the region of the program where a variable can be accessed
- LA local variable exists only inside its function
- LA global variable can be accessed from anywhere
- LLocal variables cannot be changed accidentally elsewhere, so bugs are easier to avoid
The scope of a variable is the part of the program in which that variable can be accessed. A local variable is created inside a function and exists only while that function is running, so nothing outside can see or alter it. A global variable is created outside all functions and can be read and modified from anywhere in the program. Local variables are usually preferred because their limited scope means no other part of the program can change them by accident, which removes a very common and difficult to trace source of bugs. It also means two different functions can safely use the same variable name without interfering, and it makes each function far easier to test, because its behaviour depends only on the arguments passed into it rather than on the state of the rest of the program.
- LCode that is needed more than once is written only once
- LA change only needs to be made in one place
- LEach function can be tested on its own
- LThe program becomes easier to read and understand
The first benefit is the removal of duplication. Code that is needed in several places can be written once as a function and called wherever it is required, so the program is shorter, and crucially any correction or improvement only has to be made in one place rather than being repeated in every copy, which removes the risk of leaving an old version behind. The second benefit is that a program built from small functions is far easier to develop, test and read. Each function does one clearly defined job and can be tested independently with a range of inputs until it is known to be correct, and the main program then reads as a sequence of well named steps that describe what the program does without the reader having to follow every line of detail.
- LDefines a function with a list parameter
- LCalculates the total and divides by the number of items
- LUses return
- LExplains that returning allows the value to be reused elsewhere
def mean(numbers): return sum(numbers) / len(numbers) average = mean([4, 8, 15, 16, 23]) print(f"The mean is {average:.2f}") The function should return the value rather than printing it because returning keeps the function general and reusable. The calling code can then decide what to do with the answer: display it, round it, compare it with another average, store it in a file or use it in a further calculation. A function that printed the mean directly would be locked to one use and could not be built on, and it would also be far harder to test automatically, because a test can compare a returned value against an expected one but cannot easily check what was printed.