User-Defined Functions
Python allows you to define your own functions, in addition to built-in ones like print() and len().
This improves code organization, reduces repetition, and improves readability.
A function can be called as many times as needed, from anywhere later in the same script.
The Python documentation has a guide on Defining Functions.
Purpose of Functions
Functions are reusable blocks of code that accept inputs, perform operations, and return outputs.
They are especially useful when a calculation needs to be performed multiple times with different inputs.
They can also abstract the details of a complex operation into a simpler interface.
For example, when you call math.sqrt(x) you are not particularly interested in the algorithm used to compute a square root. You just want the answer.
Syntax
A function is defined with the def keyword:
def function_name(param1, param2):
# code to compute a result
return value
Calling function_name() does nothing until you actually invoke it by name, followed by parentheses:
def greet():
print("Hello from a function!")
greet()
Hello from a function!
Parameters and Return Values
A function can accept one or more parameters, use them in its body, and send a result back with return.
Execution of the function stops as soon as return runs.
def square(x):
return x ** 2
result = square(5)
print(result)
print(square(3) + square(4))
25
25
Example: Axial Stress
Question
A force of 5000 N is applied to a beam with a cross-sectional area of 0.002 m2. Given the formula for axial stress:
\[\sigma = \frac{F}{A}\]write a Python function that calculates stress for any force and area, then use it to find the stress for the givens above. Express your answer in MPa.
Solution
def axial_stress(force, area):
"""Return axial stress (Pa) given a force (N) and cross-sectional area (m^2)."""
return force / area
sigma = axial_stress(5000, 0.002)
print(f"{sigma / 1e6:.2f} MPa")
2.50 MPa
Default Parameter Values
A parameter can be given a default value, used whenever the caller doesn’t provide one. Defaults make a function flexible without forcing every caller to specify every input:
def time_to_deplete(charge_pct, drain_rate_pct=5.0):
"""Hours until charge_pct reaches zero, at a constant drain rate."""
return charge_pct / drain_rate_pct
print(time_to_deplete(80)) # uses the default rate
print(time_to_deplete(80, drain_rate_pct=8.0)) # override by keyword
print(time_to_deplete(80, 2.5)) # override by position
16.0
10.0
32.0
Keyword vs. Positional Arguments
Arguments can be passed by position, matched to parameters in order, or by keyword, matched by name regardless of order:
def describe_stage(name, mass_kg, reusable=False):
print(f"{name}: {mass_kg} kg, reusable={reusable}")
describe_stage("Booster", 25000) # positional
describe_stage("Booster", 25000, True) # positional
describe_stage(name="Booster", mass_kg=25000, reusable=True) # keyword
describe_stage("Booster", reusable=True, mass_kg=25000) # mixed
Booster: 25000 kg, reusable=False
Booster: 25000 kg, reusable=True
Booster: 25000 kg, reusable=True
Booster: 25000 kg, reusable=True
Keyword arguments are especially useful for a function with several parameters, since reusable=True is clearer at the call site than a bare True in the third position.
Multiple Return Values
A return statement can return more than one value, separated by commas.
Python packs them into a tuple, which the caller can unpack into separate variables.
See Tuples in Depth on the Lists & Tuples page for a worked example.
Recursion
A function can call itself, which is called recursion. Each call should solve a smaller version of the problem, until it reaches a base case that can be answered directly without another call:
import math
def factorial(n):
if n <= 1:
return 1 # base case: stop recursing
return n * factorial(n - 1) # recursive case: smaller subproblem
print(factorial(5)) # 5 * 4 * 3 * 2 * 1
print(math.factorial(5)) # the standard library already has this
120
120
factorial(5) calls factorial(4), which calls factorial(3), and so on down to factorial(1), which returns 1 without recursing further.
Recursion fits naturally when a problem is defined in terms of smaller versions of itself, but for most engineering code, a loop is clearer and avoids Python’s recursion depth limit.
Functions as Values
Functions in Python are values, just like a number or a string. A function can be stored in a variable, passed as an argument to another function, and called later:
def apply_twice(func, value):
"""Call func on value, then call it again on the result."""
return func(func(value))
def add_ten(x):
return x + 10
def square(x):
return x ** 2
print(apply_twice(add_ten, 5)) # add_ten(add_ten(5)) = 25
print(apply_twice(square, 3)) # square(square(3)) = 81
25
81
apply_twice works with whatever function it’s given, calling that function twice without needing to know what it does.
A function that accepts or returns another function like this is called a higher-order function.
Variable Scope
Variables created inside a function are local to that function, and only exist while it’s running. Assigning to a variable inside a function does not affect a variable of the same name outside it:
x = 10 # this is a variable in the outer scope
def modify_x():
x = 99 # this creates a NEW local variable, it does NOT touch the
# outer x
print(f"inside function, x = {x}")
modify_x()
print(f"outside function, x = {x}") # still 10!
inside function, x = 99
outside function, x = 10
x inside modify_x is a different variable from x at the top level, even though they share a name.
To actually modify an outer variable from inside a function, you’d need the global keyword, but in this course, prefer passing values in as parameters and getting values out with return instead.
It’s much easier to reason about.
Best Practices
- Use descriptive names for functions and their parameters, so a reader can guess what they do without reading the body
- Include a docstring describing the purpose of the function and any non-obvious steps
- Avoid hardcoding values inside a function - pass them in as parameters instead, so the function stays reusable
- Keep the scope of each function narrow - one task per function
Reading Questions
- What are the benefits of user-defined functions in Python?
- What keyword ends a function’s execution and sends a value back to the caller?
- Describe which variables from a script are, and are not, available inside a function called by that script.
- How would you define a function that calculates the volume of a cylinder given its radius and height?
- What is the difference between a positional argument and a keyword argument?
- If a function is defined as
def f(a, b=10):, what value doesbtake in the callf(5)? - What is a base case, and why does a recursive function need one?
- What does it mean for a function to be a “higher-order function”?