Functions
Python functions are first-class objects — they can be assigned to variables, passed as arguments, and returned from other functions. Understanding *args, **kwargs, and closures unlocks powerful patterns.
def greet(name):
"""Return a personalised greeting."""
return f"Sawubona, {name}!"
def average(marks):
"""Return the mean of a list of numeric marks."""
if not marks:
return 0.0
return sum(marks) / len(marks)
print(greet("Thandi")) # Sawubona, Thandi!
print(average([65, 72, 80])) # 72.33...
Default Parameters and Keyword Arguments
def register_learner(
name,
course = "Undecided",
year = 1,
has_laptop = False
):
print(f"{name} | {course} | Year {year} | Laptop: {has_laptop}")
register_learner("Thandi") # all defaults
register_learner("Sipho", "Java") # course only
register_learner("Lerato", year=2, course="SQL") # keyword order any
register_learner(has_laptop=True, name="Bongani") # all keyword
# MUTABLE DEFAULT ARGUMENT BUG — common Python trap:
def bad(items=[]):
items.append(1)
return items
print(bad()) # [1]
print(bad()) # [1, 1] !!! — same list object reused across calls
# Correct pattern — use None as default:
def good(items=None):
if items is None:
items = []
items.append(1)
return items
*args and **kwargs
# *args — collect any number of positional arguments as a tuple:
def total(*marks):
return sum(marks)
print(total(65, 72, 80, 91)) # 308 — any number of args
# **kwargs — collect keyword arguments as a dict:
def print_profile(**info):
for key, value in info.items():
print(f" {key}: {value}")
print_profile(name="Thandi", city="Johannesburg", course="Python")
# Combining:
def log(message, *args, **kwargs):
print(f"[LOG] {message}", *args, **kwargs)
log("Started", end="\n") # forwards **kwargs to print
Functions as First-Class Objects
# Assign a function to a variable:
greet_fn = greet
print(greet_fn("Sipho")) # Sawubona, Sipho!
# Pass a function as an argument:
marks = [65, 72, 80, 58, 91]
# sorted() accepts a key function:
sorted_marks = sorted(marks, key=lambda m: -m) # descending
print(sorted_marks)
# Lambda — anonymous one-expression function:
to_grade = lambda m: "Pass" if m >= 50 else "Fail"
print(to_grade(74)) # Pass
print(to_grade(35)) # Fail
# Filter with lambda:
passing = list(filter(lambda m: m >= 50, marks))
print(passing)
Practice Task
Your Turn
Write a function grade_report(learners) that accepts a list of (name, mark) tuples and: returns a sorted list of (name, mark, label) tuples (sorted by mark descending), accepts an optional custom label function (defaulting to your own grade label function), accepts **kwargs that are forwarded to a print function. Test it with both default and custom label functions.
Common Mistakes
- Default mutable arguments (list, dict, set) — always use None as default.
- Forgetting return — the function silently returns None.
- Confusing *args (tuple) with **kwargs (dict).
- Lambda for multi-line logic — use a real def instead.
Professional Tip
Type-hint your function signatures for cleaner code and IDE support: def average(marks: list[float]) -> float:. Python doesn't enforce the hints but IDEs use them for autocomplete and error detection.
Mini Quiz
What does *args collect in a Python function?