Functions
def, parameters, defaults, *args, **kwargs, return values, and type hints
Functions package code so it can be reused, named, and tested. Every program is a tree of function calls. The keyword is def, the body is indented, and a function ends as soon as its indentation does.
Where are functions used?
Functions are the fundamental building block of modern programs:
- Every API — whether it's a REST endpoint, a library method, or a CLI command — is ultimately a function call with inputs and outputs.
- Testing — Unit tests verify one function at a time. Your test suite is only as good as your functions are isolated.
- Functional programming — Pure functions (same input → same output, no side effects) make code easier to reason about, parallelize, and debug.
- The call stack — Your entire program is a tree of function calls. When you see a traceback, each line is a function that called the next.
Think of every import requests; requests.get(url) or df.groupby("category").mean() — each is a function doing one job well, composed with others.
Etymology: functions vs mathematical functions
The term function comes from mathematics: f(x) = y means "given input x, produce output y."
In math, functions are pure:
f(3)always returns the same value.fdoesn't modify anything; it just computes.
Python functions can behave this way (and should, when possible):
def square(x):
return x * xBut Python functions can also have side effects (printing, writing files, mutating arguments, maintaining state). This makes them more powerful but also harder to test:
def log_and_square(x):
print(f"computing square of {x}") # side effect: I/O
return x * xWhen you hear "functional programming," it means prefer pure functions: no side effects, same input → same output, every time.
Anatomy of a function call
A function is defined once with def, then called many times. Each call creates a new local scope, binds arguments to parameters, executes the body, and returns a value (or None if no return is hit).
Defining a function
A function that does not explicitly return returns None.
Docstrings are your friend
The triple-quoted string right after def is the docstring. help(greet) will show it. Write them for any function someone else (or future you) will call.
Parameters and arguments
Python distinguishes between positional and keyword arguments.
When to use keyword arguments
Use positional for the 1–2 obvious arguments (open(filename), range(10)). Use keyword for everything else, especially booleans and numbers whose meaning isn't clear from position alone: retry(url, timeout=30, retries=3).
Default values
Defaults are evaluated once, when the function is defined (not when it's called). A mutable default value is the most famous Python footgun:
Mutable default argument gotcha
Never use a mutable object ([], {}, custom class) as a default. Use None as a sentinel and create the object inside the function.
*args and **kwargs
*args captures any extra positional arguments as a tuple; **kwargs captures any extra keyword arguments as a dict.
When calling a function, * and ** unpack iterables/dicts back into arguments:
Why *args, **kwargs?
The names args and kwargs are just convention; the * and ** are the syntax. You can write *numbers, **options if you prefer. But *args, **kwargs is so ubiquitous that everyone recognizes it instantly.
Positional-only and keyword-only parameters
Modern Python lets you control exactly how callers may pass each argument:
def func(pos_only, /, pos_or_kw, *, kw_only):
...- Parameters before
/are positional-only. - Parameters after
*are keyword-only. - Parameters between
/and*can be passed either way.
When to use / and *
Use positional-only (/) when the parameter name is an implementation detail and you want freedom to rename it later (e.g., len(obj) — who cares what the parameter is called?). Use keyword-only (*) when you have many parameters or when the meaning isn't obvious from position alone (open(file, *, encoding="utf-8")).
Return values
A return statement exits the function immediately and sends a value back to the caller.
You can return multiple values by separating them with commas. Python packs them into a tuple:
Use early return to avoid deep nesting:
Lambdas
A lambda is an anonymous, single-expression function. They are useful as key= arguments to sorted, min, max, and similar higher-order functions.
lambda vs def
For anything beyond a one-liner, prefer def. Named functions show up in tracebacks; lambdas do not. A named function also gets a docstring and is easier to test in isolation.
Type hints
Type hints (PEP 484+) are optional annotations. Python itself does not enforce them at runtime; tools like mypy, pyright, and editors use them to catch bugs and power autocompletion.
You can put them on plain variables too:
count: int = 0
names: list[str] = []
cache: dict[str, int] = {}Type hints as documentation
Type hints are living documentation that stays in sync with the code (unlike a comment, which can drift). They also unlock powerful IDE features: jump-to-definition, find-all-references, refactor-rename, etc.
First-class functions
In Python, functions are first-class objects: you can pass them as arguments, store them in data structures, and return them from other functions.
This is the foundation of decorators, callbacks, and many design patterns.
Higher-order functions: map, filter, sorted
These built-ins take a function and an iterable.
Comprehensions vs map/filter
Comprehensions are usually clearer and faster in Python. Use map/filter when you already have a named function and applying it is a one-liner (map(str.upper, words)). Otherwise, reach for a list comprehension.
Real-world patterns
Pure functions (no side effects) are easier to test and parallelize:
# pure: always returns same output for same input
def tax(amount, rate):
return amount * rate
# impure: prints (side effect)
def log_tax(amount, rate):
result = amount * rate
print(f"tax on {amount} at {rate}: {result}")
return resultHigher-order functions (functions that take or return functions) are everywhere in Python:
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)Here, lru_cache is a decorator — a function that takes a function and returns a wrapped version with caching.
Challenges
Define a function compose(f, g) that takes two single-argument functions and returns a new function h such that h(x) == f(g(x)) for any x.
Define greet_all(*names, greeting="Hello") that returns a single string greeting each name on its own line, in the form "<greeting>, <name>!". If no names are passed, return the empty string.
Example:
greet_all("Ada", "Grace", greeting="Hi")
# -> "Hi, Ada!\nHi, Grace!"
The function below has a mutable default argument bug. Fix it by using None as a sentinel and creating a new list inside the function when needed.
Define a function factorial(n) that computes n! recursively. factorial(0) should return 1.
Multiple choice questions
What does this print?
def f(x, items=[]):
items.append(x)
return items
print(f(1))
print(f(2))
[1]\n[2]
[1]\n[1, 2]
[1, 2]\n[1, 2]
A TypeError
Given def f(a, b, /, c, *, d): ..., which call is valid?
f(1, 2, 3, d=4)
f(a=1, b=2, c=3, d=4)
f(1, 2, 3, 4)
f(1, b=2, c=3, d=4)
What is the return value of a function that has no return statement?
0
None
An empty string ""
It raises a SyntaxError
Which statement about lambda is true?
lambda functions can contain multiple statements.
lambda functions are anonymous and show up as <lambda> in tracebacks.
lambda functions are faster than def functions.
lambda functions cannot take arguments.
What does unpacking with * do when calling a function?
It captures extra positional arguments into a tuple.
It expands an iterable into separate positional arguments.
It makes the argument keyword-only.
It creates a pointer to the iterable.
Why are type hints useful in Python?
They make the code run faster.
They are enforced by the Python interpreter at runtime.
They help tools like mypy catch bugs and improve IDE autocompletion.
They are required for all functions in Python 3.10+.
Next: where Python looks for names when you use them.