Why Use Functions?
Imagine you need to greet 50 different users. Without functions you'd write the same greeting code 50 times. With a function you write it once and call it 50 times. Functions solve several real problems:
Reusability
Write logic once, call it anywhere in your program β or even from other programs by importing.
Organization
Break large programs into smaller, named chunks. Each function has one clear job.
Easier Debugging
When a bug exists in a function, you fix it in one place and every call to that function is fixed instantly.
Readability
A well-named function like calculate_tax() communicates intent immediately without reading the implementation.
Defining a Function with def
Use the def keyword followed by the function name, parentheses, and a colon. The function body is indented.
# Define a simple function
def greet():
print("Hello, World!")
# Call (invoke) the function
greet()
greet() # Can call it multiple times
greet()
In Python, a function must be defined before it's called. If you call a function before its def appears in the file, you'll get a NameError. The common pattern is to define all functions at the top and call them at the bottom.
Parameters and Arguments
Parameters are the variable names listed in the function definition. Arguments are the actual values you pass when calling the function.
# 'name' is a parameter
def greet(name):
print(f"Hello, {name}!")
# "Alice" and "Bob" are arguments
greet("Alice")
greet("Bob")
# Function with multiple parameters
def describe_pet(name, species, age):
print(f"{name} is a {age}-year-old {species}.")
describe_pet("Fluffy", "cat", 3)
describe_pet("Rex", "dog", 7)
The return Statement
Functions can return a value back to the caller using return. Without a return statement (or with a bare return), Python returns None.
def add(a, b):
return a + b
result = add(10, 25)
print(result) # 35
print(add(3, 7)) # Use return value directly
# Return multiple values (as a tuple)
def min_max(numbers):
return min(numbers), max(numbers)
low, high = min_max([4, 1, 9, 2, 7])
print(f"Min: {low}, Max: {high}")
# Function with no return (returns None)
def say_hello():
print("Hello!")
value = say_hello()
print(value) # None
As soon as Python hits a return statement, it exits the function. Any code after return in the same block is unreachable (dead code). Use early returns intentionally to handle edge cases at the top of a function.
Docstrings β Documenting Your Functions
A docstring is a string placed as the first statement inside a function body. It documents what the function does, its parameters, and what it returns. You access it via function.__doc__ or the built-in help().
def calculate_area(length, width):
"""
Calculate the area of a rectangle.
Args:
length (float): The length of the rectangle.
width (float): The width of the rectangle.
Returns:
float: The area (length Γ width).
"""
return length * width
# Access the docstring
print(calculate_area.__doc__)
# Or use help()
help(calculate_area)
Default Parameters
You can give parameters default values. If the caller doesn't provide that argument, the default is used. Parameters with defaults must come after parameters without defaults.
def greet(name, greeting="Hello", punctuation="!"):
print(f"{greeting}, {name}{punctuation}")
# Use all defaults
greet("Alice") # Hello, Alice!
# Override one default
greet("Bob", greeting="Hi") # Hi, Bob!
# Override all defaults
greet("Carol", "Good morning", ".") # Good morning, Carol.
# More practical example
def create_user(username, role="viewer", active=True):
return {
"username": username,
"role": role,
"active": active
}
admin = create_user("admin", role="admin")
guest = create_user("guest123")
print(admin)
print(guest)
Never use a mutable object (list, dict) as a default argument. The default is created once when the function is defined, not on each call. Use None as the default and create the mutable object inside the function instead:
# BAD - shares the same list across all calls!
def add_item(item, items=[]):
items.append(item)
return items
# GOOD - create a fresh list each call
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
*args β Variable Positional Arguments
When you prefix a parameter with *, it collects any extra positional arguments into a tuple. The name args is a convention β the * is what matters.
def add_all(*args):
"""Add any number of numbers together."""
print(f"Arguments received: {args}") # it's a tuple
return sum(args)
print(add_all(1, 2)) # 3
print(add_all(1, 2, 3, 4)) # 10
print(add_all(10, 20, 30, 40, 50)) # 150
# Mixing regular and *args
def introduce(greeting, *names):
for name in names:
print(f"{greeting}, {name}!")
introduce("Hello", "Alice", "Bob", "Carol")
**kwargs β Variable Keyword Arguments
When you prefix a parameter with **, it collects any extra keyword arguments into a dictionary. The name kwargs is a convention.
def print_info(**kwargs):
"""Print any key-value information."""
print(f"Info received: {kwargs}") # it's a dict
for key, value in kwargs.items():
print(f" {key}: {value}")
print_info(name="Alice", age=30, city="London")
print()
# Practical use: building flexible configuration
def create_profile(username, **options):
profile = {"username": username}
profile.update(options)
return profile
p1 = create_profile("alice", theme="dark", language="en")
p2 = create_profile("bob", premium=True, notifications=False, timezone="UTC")
print(p1)
print(p2)
Combining All Parameter Types
You can combine all parameter types in one function. The order must be: regular parameters β *args β keyword-only parameters β **kwargs.
def full_example(required, default="ok", *args, keyword_only="yes", **kwargs):
print(f"required: {required}")
print(f"default: {default}")
print(f"args: {args}")
print(f"keyword_only: {keyword_only}")
print(f"kwargs: {kwargs}")
full_example(
"must",
"custom",
"extra1", "extra2",
keyword_only="changed",
x=1, y=2
)
Complete Practical Example
def calculate_invoice(customer, *items, discount=0, tax_rate=0.08):
"""
Calculate a customer invoice total.
Args:
customer (str): Customer name.
*items: Variable number of (item_name, price) tuples.
discount (float): Discount as a decimal (e.g. 0.1 for 10%).
tax_rate (float): Tax rate as a decimal. Default 8%.
Returns:
dict: Invoice breakdown.
"""
subtotal = sum(price for _, price in items)
discount_amount = subtotal * discount
taxable = subtotal - discount_amount
tax = taxable * tax_rate
total = taxable + tax
return {
"customer": customer,
"items": list(items),
"subtotal": round(subtotal, 2),
"discount": round(discount_amount, 2),
"tax": round(tax, 2),
"total": round(total, 2)
}
invoice = calculate_invoice(
"Alice Corp",
("Website Design", 1200.00),
("Logo Design", 350.00),
("Hosting Setup", 150.00),
discount=0.10,
tax_rate=0.07
)
for key, value in invoice.items():
print(f" {key:<12}: {value}")
ποΈ Practical Exercise
- Write a function
celsius_to_fahrenheit(celsius)that converts temperature and returns the result. - Write a function
is_even(n)that returnsTrueifnis even,Falseotherwise. - Write a function
repeat_string(text, times=2)that returns the string repeatedtimestimes. Default to 2. - Write a function
sum_all(*numbers)that accepts any number of arguments and returns their sum. - Write a function
build_tag(**attributes)that accepts keyword arguments and returns an HTML-like string, e.g.build_tag(class_="btn", id="submit")β'class_="btn" id="submit"'.
π₯ Challenge
Build a complete grade calculator program using functions. Create a function letter_grade(score) that returns "A", "B", "C", "D", or "F". Create a function class_report(*students) where each student is a tuple of (name, score). The function should print a formatted table with each student's name, score, and letter grade, plus overall class average. Use docstrings on both functions.
Interview Questions on Python Functions
- What is the difference between a parameter and an argument?
- What does a function return when there is no explicit
returnstatement? - What is a docstring and how do you access it programmatically?
- What is the danger of using a mutable default argument like a list? How do you fix it?
- Explain the difference between
*argsand**kwargs. What types do they produce inside the function? - Can a function return multiple values? How does Python handle this?
- What is the correct order of parameter types in a Python function signature?
- What is the difference between a keyword argument and a positional argument when calling a function?
π Summary
- Define functions with
def function_name(parameters):; the body is indented. - Call a function by writing its name followed by parentheses and any arguments.
- Docstrings (triple-quoted strings as the first statement) document what a function does.
- Default parameters (
def f(x=10)) make arguments optional β put them after required parameters. - Never use mutable objects (lists, dicts) as default argument values β use
Noneinstead. *argscollects extra positional arguments into a tuple.**kwargscollects extra keyword arguments into a dict.- The
returnstatement exits the function and optionally passes a value back; without it,Noneis returned. - Parameter order: required β defaults β
*argsβ keyword-only β**kwargs.
Related Topics
Frequently Asked Questions
Yes! Functions are first-class objects in Python, meaning they can be assigned to variables, stored in lists, and passed as arguments. This is the basis for higher-order functions like map(), filter(), and sorted(key=...).
print() displays a value to the screen β it doesn't give the value to the caller. return passes the value back to whoever called the function, where it can be stored in a variable or used in an expression. A function that only prints is not reusable in calculations.
Yes β this is called recursion. A function that calls itself must have a base case (a condition where it stops calling itself) to avoid infinite recursion and a RecursionError. Recursion is covered in its own lesson.
Technically one β but that value can be a tuple containing multiple items. When you write return a, b, c, Python packs those into a tuple (a, b, c). The caller can unpack it with x, y, z = my_function().
A keyword-only argument is a parameter that can only be passed by name, not by position. You define them after a bare * or after *args: def f(a, *, b):. Calling f(1, 2) raises a TypeError; you must write f(1, b=2). They make function calls more explicit and self-documenting.