Functions in Python
Defining and calling functions, parameters and return values, default and keyword arguments, scope, docstrings, functions as values, lambda, *args and **kwargs, and a first look at decorators.
Why functions
A function is a named block of code that does one job. You define it once with def and call it as often as you like by writing its name followed by parentheses.
Functions are how programs stay manageable:
- No repetition: fix a bug in one place instead of five copies.
- Names for ideas:
is_valid_email(address)says what happens; ten lines of string checks do not. - Testable pieces: a function with clear inputs and outputs can be checked on its own.
The def line names the function and lists its parameters in parentheses, then ends with a colon. The indented body runs only when the function is called, not when it is defined.
A reusable, named block of code. It receives input through parameters, runs its body when called, and hands a result back to the caller with return.
1def greet(name):2 print(f"Hello, {name}!")34greet("Ada")5greet("Grace")6print("greet is defined:", callable(greet))
Hello, Ada! Hello, Grace! greet is defined: True
Parameters and arguments
Parameters are the names in the def line; arguments are the actual values passed in a call. When a function is called, each parameter is bound to its argument, and the body runs with those names.
Arguments can be passed by position (matched in order) or by keyword (name=value, matched by name). Keyword arguments make calls self-explanatory, especially for booleans and numbers: resize(image, 800, 600, keep_ratio=True) is clearer than four bare values. Positional arguments must come before keyword arguments in a call.
Calling with the wrong number of arguments raises a TypeError that names the missing or unexpected argument.
1def describe_pet(name, species, age):2 print(f"{name} is a {age}-year-old {species}.")34describe_pet("Milo", "cat", 3)5describe_pet(age=5, name="Rex", species="dog")6describe_pet("Kiwi", age=1, species="parrot")
Milo is a 3-year-old cat. Rex is a 5-year-old dog. Kiwi is a 1-year-old parrot.
Returning a result
return ends the function and hands a value back to the caller, where it can be stored, printed or used in another expression. A function without a return (or with a bare return) gives back None.
Learn the difference between printing and returning early. print shows text to a person and the program cannot use it afterwards. return gives a value to the code that called the function. Functions that calculate something should return it and leave the printing to the caller.
To return several values, separate them with commas. Python packs them into a tuple, and the caller can unpack it directly.
1def area_print(width, height):2 print(width * height)34def area(width, height):5 return width * height67result = area_print(3, 4)8print("area_print returned", result)9total = area(3, 4) + area(2, 5)10print("Total area:", total)1112def min_max(values):13 return min(values), max(values)1415low, high = min_max([7, 2, 9, 4])16print(low, high)
12 area_print returned None Total area: 22 2 9
Default parameter values
A parameter can have a default value, used when the caller leaves that argument out: def greet(name, greeting="Hello"). Parameters with defaults must come after those without.
One trap is famous enough to know by heart: default values are evaluated once, when the function is defined, not on every call. A mutable default such as [] is therefore shared between calls and keeps growing. Use None as the default and create the list inside the function.
1def greet(name, greeting="Hello"):2 return f"{greeting}, {name}!"34print(greet("Ada"))5print(greet("Ada", greeting="Welcome back"))67def add_bad(item, basket=[]):8 basket.append(item)9 return basket1011print(add_bad("apple"))12print(add_bad("pear"))1314def add_good(item, basket=None):15 if basket is None:16 basket = []17 basket.append(item)18 return basket1920print(add_good("apple"))21print(add_good("pear"))
Hello, Ada! Welcome back, Ada! ['apple'] ['apple', 'pear'] ['apple'] ['pear']
Scope: where a name is visible
Names created inside a function (its parameters and any variable it assigns) are local: they exist only while the function runs and are invisible outside it. Names defined at the top level of the file are global and can be read inside functions.
When Python looks up a name it searches in this order, remembered as LEGB: Local, then Enclosing functions, then Global, then Built-in (print, len, ...).
Assigning to a name inside a function makes it local to that function, even if a global with the same name exists. The global keyword can override this, but functions that change global variables are hard to reason about. Prefer passing values in as arguments and returning results.
A function that only uses its parameters and returns a value, without touching anything outside, is called pure. Pure functions are the easiest to test and reuse.
1rate = 0.223def tax(amount):4 result = amount * rate5 return result67print(tax(150))8print("rate" in globals(), "result" in globals())910count = 101112def reset():13 count = 014 return count1516print(reset(), count)
30.0 True False 0 10
Docstrings and designing good functions
The first statement of a function can be a string literal, its docstring. It documents what the function does, what it expects and what it returns, and tools such as help() and editors display it.
Good functions share a few traits:
- One job, described by a verb-based name:
parse_date,calculate_total,is_weekend. - Small: if you need "and" to describe it, consider splitting it.
- Inputs in, output out: parameters and a return value rather than global variables,
input()andprintburied inside. - Guard clauses: reject invalid input at the top with an early
return(or an exception), so the main logic is not nested inside conditions.
1def average(values):2 """Return the mean of a list of numbers, or None for an empty list."""3 if not values:4 return None5 return sum(values) / len(values)67print(average([4, 8, 6]))8print(average([]))9print(average.__doc__)
6.0 None Return the mean of a list of numbers, or None for an empty list.
Functions are values
In Python a function is an object like any other: you can store it in a variable, put it in a list or dictionary, and pass it to another function. Functions that take other functions as arguments are called higher-order functions.
The most common case is the key argument of sorted, min and max: you pass a function that tells them what to compare. For tiny one-off functions, a lambda expression creates an unnamed function inline: lambda word: len(word). A lambda can only contain a single expression; anything longer deserves a def and a name.
map(f, items) applies f to every item, and filter(f, items) keeps the items for which f returns a truthy value. Both return lazy iterators, so wrap them in list() to see the results.
1def shout(text):2 return text.upper() + "!"34say = shout5print(say("hi"))67words = ["banana", "Kiwi", "apple", "fig"]8print(sorted(words))9print(sorted(words, key=str.lower))10print(sorted(words, key=lambda w: (len(w), w.lower())))11print(max(words, key=len))12print(list(map(len, words)))13print(list(filter(lambda w: len(w) > 3, words)))1415operations = {"double": lambda x: x * 2, "square": lambda x: x * x}16print(operations["square"](7))
HI! ['Kiwi', 'apple', 'banana', 'fig'] ['apple', 'banana', 'fig', 'Kiwi'] ['fig', 'Kiwi', 'apple', 'banana'] banana [6, 4, 5, 3] ['banana', 'Kiwi', 'apple'] 49
Flexible arguments: *args and **kwargs
Sometimes a function should accept any number of arguments, like print does. A parameter written *args collects all extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dictionary. The names args and kwargs are conventions; the stars are what matter.
The same stars work in the other direction in a call: f(*values) spreads a list into positional arguments and f(**options) spreads a dictionary into keyword arguments.
1def total(*numbers):2 return sum(numbers)34print(total(1, 2, 3), total())56def make_tag(name, **attributes):7 parts = [name] + [f'{k}="{v}"' for k, v in attributes.items()]8 return "<" + " ".join(parts) + ">"910print(make_tag("img", src="cat.png", alt="A cat"))1112point = [3, 4]13print(total(*point))
6 0 <img src="cat.png" alt="A cat"> 7
Functions that wrap functions
A function defined inside another function can use the outer function's variables even after the outer function has returned. Such an inner function is a closure. Closures make it possible to write functions that take a function and return an improved version of it.
That pattern is a decorator. Writing @log_call above a def is shorthand for name = log_call(name): the function is replaced by a wrapper that adds behaviour (logging, timing, checking permissions, caching) and then calls the original. functools.wraps copies the original's name and docstring onto the wrapper.
You will use decorators from libraries long before you write many of your own, so the goal here is to recognise what @something means.
1import functools23def log_call(func):4 @functools.wraps(func)5 def wrapper(*args, **kwargs):6 result = func(*args, **kwargs)7 print(f"{func.__name__}{args} -> {result}")8 return result9 return wrapper1011@log_call12def add(a, b):13 return a + b1415add(2, 3)16add(10, -4)17print(add.__name__)
add(2, 3) -> 5 add(10, -4) -> 6 add
Check your understanding
What does `print(f(2))` show if `def f(x): x * 2` (no return)?
Practice
Decompose problems into small functions (a password checker and an ISBN validator), use higher-order functions, build a decorator, and design a calculator engine with explicit state.
Password Strength Checker
Reveal Solution & AnalysisHide Solution
ISBN-10 Validator
Reveal Solution & AnalysisHide Solution
Lambda, Map, Filter, Reduce
Reveal Solution & AnalysisHide Solution
Function Decorators: Call Logger
Reveal Solution & AnalysisHide Solution
Calculator Engine for a Tkinter GUI
Reveal Solution & AnalysisHide Solution
Quick Syntax & Reference Sheet
| Concept / Rule | Syntax / Usage |
|---|---|
| Define and call | def area(w, h):
return w * h
area(3, 4) |
| Default value | def greet(name, greeting="Hello"): |
| Keyword argument | greet("Ada", greeting="Hi") |
| Return several values | return low, high |
| Docstring | """Return the mean of values.""" |
| Lambda as a key | sorted(people, key=lambda p: p[1]) |
| Any number of arguments | def f(*args, **kwargs): |
| Decorator | @functools.wraps(func) inside a wrapper |
Common Pitfalls & Developer Mistakes
Printing instead of returning
Why it happens: The caller receives None and cannot use the result.
Forgetting the parentheses when calling
Why it happens: greet refers to the function object; greet() calls it.
Mutable default arguments
Why it happens: def f(items=[]) shares one list between calls.
Changing a global inside a function without realising it creates a local
Why it happens: count = count + 1 inside a function raises UnboundLocalError.