1
Defining Functions
▶Functions are one of the most fundamental building blocks in Python. They allow you to group reusable logic under a single name, making programs easier to read, test, and maintain. Understanding how to define and call functions correctly — and how to document them well — is essential for writing clean, professional Python code.
- The
defkeyword signals the start of a function definition. When Python encountersdef, it creates a new function object and binds it to the name you provide, but does not execute the body yet. - The function name should follow snake_case naming conventions per Python style guides. This means using all lowercase letters with words separated by underscores, for example
calculate_totalorget_user_input. - The colon at the end of the signature is required and introduces the indented body block. Every statement belonging to the function must be consistently indented beneath the
defline, and Python will raise anIndentationErrorif this rule is violated. - Defining a function with the same name twice in the same scope silently overwrites the first definition. This can be a subtle source of bugs, especially in larger files, so it is important to keep function names unique within a given scope.
returnends function execution and passes the specified value to the caller. As soon as areturnstatement is reached, the function stops running and control is handed back to wherever the function was called from.- Omitting
returnor writingreturnalone causes the function to returnNone. This is Python's way of representing the absence of a meaningful value, and it is easy to accidentally use such a function's result as if it were a real value. - A function can contain multiple
returnstatements to handle different conditions. For example, an earlyreturncan be used to exit quickly when input is invalid, keeping the main logic uncluttered. - The returned value can be assigned to a variable or used directly in an expression. For instance, you can write
total = add(3, 4)or embed the call inside a larger expression likeprint(add(3, 4) * 2).
- Docstrings are written as triple-quoted strings immediately after the
defline. They are the standard way to embed documentation directly into the function itself, rather than relying solely on comments elsewhere in the file. - They describe the function's purpose, parameters, and return value. A well-structured docstring answers the questions: what does this function do, what does it expect, and what does it give back?
- The built-in
help()function displays a function's docstring interactively. Callinghelp(my_function)in the Python REPL renders the docstring in a readable format, making it invaluable during exploration and debugging. - Well-written docstrings improve code maintainability and team collaboration. When you or a colleague returns to a function weeks later, a clear docstring removes the need to reverse-engineer the code's intent.
- Parentheses after the function name trigger execution; without them, the name refers to the function object itself. Writing
greet()calls the function, while writinggreetwithout parentheses simply references the object, which can be stored, passed, or printed. - Arguments supplied at the call site are matched to the function's parameters. Python takes the values you provide inside the parentheses and maps them onto the corresponding parameter names defined in the function signature.
- The call expression evaluates to whatever the function returns. This means a function call can appear anywhere a value is expected — in assignments, conditions, print statements, or other function calls.
- Functions can call other functions, enabling modular and layered program design. Breaking a complex task into smaller functions that call one another keeps each piece focused and independently testable.
# Basic function definition with a docstring
def greet_user(name):
"""Return a personalised greeting string for the given name."""
return f"Hello, {name}!"
# Calling the function and using its return value
message = greet_user("Alice")
print(message) # Hello, Alice!
# help() displays the docstring
help(greet_user)
NotesIntroduces the syntax and mechanics of creating reusable functions in Python. Covers the def keyword, naming conventions, and the role of the return statement. Emphasise to students that forgetting the colon or inconsistent indentation are the most common early mistakes. The silent overwrite of a function name is worth a live demonstration.
2
Parameters and Arguments
▶Once you know how to define a function, the next step is understanding the many ways Python lets you supply values to it. Python offers positional arguments, keyword arguments, and default parameter values — each with its own rules and best-use scenarios. Getting the terminology straight between parameters and arguments also helps you read error messages accurately.
- Positional arguments must be supplied in the exact order the parameters are declared. Python assigns each argument to the parameter at the same position in the signature, so swapping the order of your values changes what each parameter receives.
- Too few or too many positional arguments raise a
TypeErrorat runtime. Python's error message will tell you how many arguments were expected versus how many were given, making these mistakes easy to diagnose. - Positional parameters are the most common and simplest form of argument passing. They work well when the meaning of each argument is obvious from context or when the function has only one or two parameters.
- The number of positional arguments must match the number of non-default parameters. Any parameter that does not have a default value must receive an argument at every call site, or Python will raise an error.
- Keyword arguments use the syntax
parameter_name=valueat the call site. By naming the parameter you are targeting, you make the intent of each argument explicit without the reader needing to check the function signature. - They can be supplied in any order regardless of the parameter declaration order. This flexibility is especially valuable when a function has many parameters and you only need to specify a few of them by name.
- Mixing positional and keyword arguments is allowed, but positional must precede keyword. Writing
func(10, y=20)is valid, butfunc(x=10, 20)is aSyntaxErrorbecause a positional argument cannot follow a keyword argument. - Keyword arguments make function calls self-documenting and reduce reliance on documentation. A call like
connect(host="localhost", port=8080)communicates its meaning immediately, even without reading the function definition.
- Default values are assigned with the syntax
parameter=valuein the function signature. For example,def send(message, retries=3)meansretrieswill be3unless the caller provides a different value. - Parameters with defaults must appear after all parameters without defaults. Placing a defaulted parameter before a required one is a
SyntaxError, because Python would have no way to determine which arguments were intended for which parameters. - Default values are evaluated at definition time, not at each call. This means a mutable default like a list is created once and shared across all calls, which can lead to unexpected behaviour if it is modified inside the function.
- Using
Noneas a default and initialising mutable defaults inside the body avoids shared-state bugs. The idiomif items is None: items = []inside the function body ensures a fresh list is created on every call that does not supply one. - Callers can override a default by supplying the argument explicitly. Defaults represent sensible fallback values, but they should never prevent a caller from customising behaviour when needed.
- Parameters live in the function signature and act as placeholders for incoming values. They are local variable names that only exist within the function's scope and receive their values when the function is called.
- Arguments are the concrete values provided at the call site. They are the actual data — numbers, strings, objects — that the function will work with during that particular execution.
- A function may have zero or more parameters and is called with a matching set of arguments. A function with no parameters, like
def say_hello():, must always be called with empty parentheses and no arguments. - Error messages from Python typically refer to arguments when describing call mismatches. For example, "takes 2 positional arguments but 3 were given" — understanding this terminology helps you locate and fix the mistake quickly.
# Positional arguments — order matters
def describe_pet(animal, name):
"""Print a simple description of a pet."""
print(f"I have a {animal} named {name}.")
describe_pet("dog", "Rex") # positional
describe_pet(name="Rex", animal="dog") # keyword — order swapped, still works
# Default parameter value
def power(base, exponent=2):
"""Return base raised to exponent (default: squared)."""
return base ** exponent
print(power(5)) # uses default → 25
print(power(5, 3)) # overrides default → 125
# Avoiding the mutable default trap
def append_item(item, collection=None):
"""Append item to collection safely."""
if collection is None:
collection = []
collection.append(item)
return collection
NotesExplores the different ways values can be passed into functions, including positional, keyword, and default parameters. Establishes clear terminology between parameters and arguments. The mutable default argument trap is worth a live demonstration — show what happens with <code>def bad(x, items=[])</code> called multiple times. Reinforce the parameter vs. argument distinction early, as students often use the terms interchangeably when asking for help.