functions
Functions — design, patterns and best practice
Function design, patterns, and best practices.
Defining and calling functions (Python examples)
def greet(name):
"""Return a greeting for name.
Args:
name (str): the person's name
Returns:
str: greeting text
"""
return f"Hello, {name}!"
message = greet("Ada")
print(message)
Parameters, defaults and named arguments
- Functions can have positional parameters, default values, and keyword-only arguments.
- Use defaults carefully — avoid mutable defaults (use
Nonepattern).
def append_unique(lst=None, value=None):
if lst is None:
lst = []
if value not in lst:
lst.append(value)
return lst
*args and **kwargs (varargs)
- Use
*argsfor variable positional arguments and**kwargsfor variable keyword arguments. They are handy for wrapper functions and APIs.
def log_msg(prefix, *args, **kwargs):
print(prefix, *args, **kwargs)
Pure vs impure functions
- Pure function: same input -> same output, no side-effects. Easier to test and reason about.
- Impure function: reads/writes external state (files, global variables, network). Keep impurity at the edges of your program.
Recursion and when to use it
- Recursion expresses divide-and-conquer solutions naturally (e.g., tree traversal, factorial). Python recursion depth is limited; prefer iterative approaches for large inputs.
def factorial(n):
if n <= 1:
return 1
return n * factorial(n-1)
Higher-order functions and closures
- Functions that accept or return other functions enable composition patterns. Closures capture variables from the outer scope.
def make_multiplier(n):
def mul(x):
return x * n
return mul
double = make_multiplier(2)
print(double(5)) # 10
Simple decorator example (Python)
def timeit(fn):
import time
def wrapper(*args, **kwargs):
t0 = time.time()
res = fn(*args, **kwargs)
print(f"{fn.__name__} took {time.time()-t0:.6f}s")
return res
return wrapper
@timeit
def work(n):
return sum(range(n))
work(1000000)
Docstrings and small tests
- Write a short docstring describing arguments, return value, and side-effects. Include a short example if helpful.
- Use simple
assertstatements or a small test file withpytestor built-inunittestto check behaviour.
Structuring code with functions
- Keep functions small and focused (single responsibility).
- If code needs more than ~40 lines, consider splitting into helper functions.
Error handling in functions
- Validate inputs and raise appropriate exceptions (
ValueError,TypeError) rather than returning sentinel values unless specified.
Exercises
- Refactor the grade calculator program into:
read_scores,compute_averages,assign_grade,write_reportfunctions with docstrings and small tests. - Implement a memoized Fibonacci using a decorator or a cache dict.
See data-structures.md for structures functions commonly operate on, and io-and-debugging.md for testing and running functions safely.