Step 05 of 10
Functions
Package code into reusable functions with parameters and return values, understand scope, and break problems into small named steps.
As programs grow, the same few lines start appearing in several places, and a 200-line file becomes hard to follow. Functions fix both problems. A function is a named, reusable piece of code: you write it once, give it a clear name, and call it wherever you need it. Good functions are also the main way programmers break a big problem into small ones they can think about one at a time. This lesson covers how to define and call functions, parameters and return values, scope, and how to decide what deserves a function.
Defining and calling a function
You've been calling functions since your first program: print, input, len, int. Now you'll write your own with def:
def greet():
print("Hello!")
print("Welcome to the program.")
greet()
greet()Hello!
Welcome to the program.
Hello!
Welcome to the program.def defines the function: its name, parentheses, a colon, and an indented body. Defining a function doesn't run it. The body runs each time you call it, by writing its name followed by (). A function must be defined before the line that calls it runs, which is why definitions usually sit at the top of a file.
Parameters: giving a function input
Parameters are variables listed in the parentheses. When you call the function, you pass arguments, which become the parameters' values for that call:
def greet(name, times):
for _ in range(times):
print(f"Hello, {name}!")
greet("Ana", 2)
greet("Ben", 1)Arguments are matched to parameters by position: "Ana" goes to name, 2 to times. (The _ is a conventional name for a loop variable you don't use.)
You can also pass arguments by name, which makes calls easier to read, and give parameters default values, which makes them optional:
def greet(name, times=1, punctuation="!"):
for _ in range(times):
print(f"Hello, {name}{punctuation}")
greet("Ana") # uses both defaults
greet("Ben", times=3)
greet("Cy", punctuation="?")return: getting a result back
Most useful functions calculate something and return it to the caller:
def area_of_rectangle(width, height):
return width * height
room = area_of_rectangle(4, 5)
hall = area_of_rectangle(1.5, 8)
print(f"Total floor area: {room + hall} m²") # Total floor area: 32.0 m²When Python reaches return, the function stops immediately and the value becomes the result of the call expression. That's why you can store it, add it, or pass it straight into another function: print(area_of_rectangle(2, 3)).
Returning vs printing
This is the most common confusion for new programmers. Compare:
def add_print(a, b):
print(a + b)
def add_return(a, b):
return a + b
x = add_print(2, 3) # prints 5
y = add_return(2, 3) # prints nothing
print(x) # None
print(y) # 5print shows a value to the human at the screen; the program can't use it afterwards. return hands the value back to the code that called the function. A function without a return returns the special value None, meaning "nothing".
Prefer functions that return values and let the caller decide what to do with them, such as print them, save them or use them in a calculation. That makes functions reusable and testable.
A function can return early, which often makes logic clearer:
def describe_temperature(celsius):
if celsius < 0:
return "freezing"
if celsius < 15:
return "cold"
if celsius < 25:
return "mild"
return "hot"Scope: variables inside functions
Variables created inside a function are local: they exist only while the function runs and can't be seen from outside.
def calculate_total(prices):
total = 0
for p in prices:
total += p
return total
print(calculate_total([3, 4, 5])) # 12
print(total) # NameError: name 'total' is not definedThat's a feature, not a limitation. Each function is a sealed box: you can understand it by reading only its body and parameters, and two functions can both use a variable called total without interfering.
A function can read variables defined at the top level of the file (called global variables), but relying on that makes functions harder to understand and test. Pass what a function needs as parameters, and return what it produces.
Docstrings: saying what a function does
The first line of a function's body can be a string that describes it, called a docstring:
def bmi(weight_kg, height_m):
"""Return the body mass index for a weight in kilograms and a height in metres."""
return weight_kg / height_m ** 2Editors show docstrings when you hover over a call, and help(bmi) in the REPL prints it. A one-line docstring saying what the function returns, and in what units, prevents a lot of bugs.
Breaking problems into functions
Functions are a thinking tool, not just a way to avoid repetition. Faced with a big problem, describe it as a few steps, then write one function per step. Here's a program that reports on a list of exam scores:
def average(numbers):
"""Return the mean of a non-empty list of numbers."""
return sum(numbers) / len(numbers)
def letter_grade(score):
"""Return A-F for a score from 0 to 100."""
if score >= 90:
return "A"
if score >= 80:
return "B"
if score >= 70:
return "C"
if score >= 60:
return "D"
return "F"
def report(scores):
"""Print a summary of a list of scores."""
avg = average(scores)
print(f"Students: {len(scores)}")
print(f"Average: {avg:.1f} ({letter_grade(avg)})")
print(f"Highest: {max(scores)}")
print(f"Lowest: {min(scores)}")
report([72, 95, 88, 64, 91])Students: 5
Average: 82.0 (B)
Highest: 95
Lowest: 64Each function does one thing and has a name that says what. report reads almost like the description of the problem. If the grading rules change, you know exactly which function to edit. (sum, max and min are built-in functions; lists, written with square brackets, are the subject of the next lesson.)
Signs that a piece of code wants to be a function:
- You've copied the same lines to a second place.
- You can describe what a block does in a short phrase ("work out the shipping cost"). That phrase is its name.
- A function has grown past what fits on one screen.
Practice
Write dates.py with three functions, each building on the previous one:
is_leap_year(year)returnsTrueorFalse. A year is a leap year if it's divisible by 4, except years divisible by 100, which aren't, except years divisible by 400, which are. (2024 and 2000 are leap years; 1900 isn't.)days_in_month(year, month)returns the number of days in that month (month is 1 to 12).day_of_year(year, month, day)returns which day of the year a date is: 1 January is 1, 31 December is 365 or 366.
At the bottom, check your functions by printing a few results whose answers you know.
Show solution
# dates.py
def is_leap_year(year):
"""Return True if year is a leap year in the Gregorian calendar."""
if year % 400 == 0:
return True
if year % 100 == 0:
return False
return year % 4 == 0
def days_in_month(year, month):
"""Return the number of days in a month (1-12) of a given year."""
if month == 2:
return 29 if is_leap_year(year) else 28
if month in (4, 6, 9, 11):
return 30
return 31
def day_of_year(year, month, day):
"""Return the day number within the year, from 1 to 365 or 366."""
total = 0
for m in range(1, month):
total += days_in_month(year, m)
return total + day
print(is_leap_year(2024), is_leap_year(1900), is_leap_year(2000)) # True False True
print(days_in_month(2023, 2), days_in_month(2024, 2), days_in_month(2024, 4)) # 28 29 30
print(day_of_year(2024, 1, 1)) # 1
print(day_of_year(2024, 3, 1)) # 61 (31 + 29 + 1)
print(day_of_year(2023, 12, 31)) # 365
print(day_of_year(2024, 12, 31)) # 366Checking 400 first, then 100, then 4 turns the rule's "except... except..." into three early returns, from most specific to least. 29 if is_leap_year(year) else 28 is a conditional expression: a compact if/else that produces a value. day_of_year reuses days_in_month, which reuses is_leap_year: small functions combining into bigger ones is exactly how larger programs are built.
Key takeaways
def name(parameters):defines a function; calling it withname(arguments)runs its body.- Arguments can be positional or named, and parameters can have default values.
returnsends a value back and ends the function; a function withoutreturngivesNone. Prefer returning over printing.- Variables inside a function are local; pass inputs as parameters instead of relying on globals.
- Break problems into small functions with clear names and docstrings; that's how big programs stay understandable.
Check yourself
Quiz
0/4 correct · 4 left
1.def double(n): print(n * 2). What is the value of result after result = double(4)?
2.What happens when a function reaches a return statement in the middle of a loop?
3.def send(to, subject="Hi", urgent=False): ... Which call sets urgent to True and keeps the default subject?
4.Why is a function that takes inputs as parameters and returns a result easier to work with than one that reads and changes global variables?
Finished reading?
Mark it complete to fill in your roadmap.