Functions are reusable blocks of code that perform a defined task. They reduce repetition, give meaningful names to operations, separate responsibilities, and make programs easier to test and maintain.
This guide explains Python functions from the first def statement to parameters, return values, defaults, keyword arguments, scope, type hints, docstrings, flexible arguments, and practical design habits.
Why Functions Matter
Without functions, a program often repeats the same instructions in several places. A small change then requires editing every copy, and inconsistent versions create bugs.
price = 80
quantity = 3
discount = 0.10
subtotal = price * quantity
final_total = subtotal - subtotal * discount
print(final_total)Turn the calculation into a reusable operation:
def calculate_total(price, quantity, discount):
subtotal = price * quantity
return subtotal - subtotal * discount
order_total = calculate_total(80, 3, 0.10)
print(order_total)The function now describes the intention and can be called with different values. The official Python tutorial on functions is the primary language reference.
Define and Call a Function
def greet():
print("Hello!")
greet()def starts the definition, greet is the function name, parentheses contain parameters, and the indented block is the function body. Defining the function does not execute it; the call greet() does.
Parameters and Arguments
A parameter is a name in the function definition. An argument is the value supplied during a call.
def greet(name):
print(f"Hello, {name}!")
greet("Maya")
greet("Leo")Use multiple parameters when the task needs several inputs:
def rectangle_area(width, height):
return width * height
print(rectangle_area(5, 3))Return Values
return sends a value back to the caller. This is different from printing it.
def add(a, b):
return a + b
result = add(4, 6)
print(result * 2)A function without an explicit return statement returns None.
def show_message(message):
print(message)
value = show_message("Done")
print(value) # NoneThe Python None guide explains this special value and correct comparisons.
Return More Than One Value
Python can return several values as a tuple.
def minimum_and_maximum(values):
return min(values), max(values)
lowest, highest = minimum_and_maximum([8, 3, 12, 5])
print(lowest)
print(highest)For many related outputs, a dictionary, named tuple, or data class may be clearer than a long tuple.
Default Parameters
A default makes an argument optional.
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
print(greet("Maya"))
print(greet("Maya", "Welcome"))Required parameters must appear before default parameters. Avoid mutable defaults such as lists.
# Problematic: the same list is reused
# def add_item(item, items=[]):
# items.append(item)
# return items
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return itemsPositional and Keyword Arguments
def create_user(name, age, active=True):
return {"name": name, "age": age, "active": active}
user1 = create_user("Sam", 28)
user2 = create_user(age=31, name="Ava", active=False)Keyword arguments make calls more readable and allow a different order. Do not place a positional argument after a keyword argument.
Keyword-Only Parameters
Add * to require keywords for selected parameters.
def calculate_price(subtotal, *, discount=0, tax=0):
discounted = subtotal * (1 - discount)
return discounted * (1 + tax)
final = calculate_price(100, discount=0.10, tax=0.05)
print(final)This prevents confusing calls such as calculate_price(100, 0.10, 0.05), where the meaning of each number is unclear.
*args for Extra Positional Arguments
def average(*numbers):
if not numbers:
raise ValueError("At least one number is required")
return sum(numbers) / len(numbers)
print(average(10, 20, 30))numbers is a tuple containing all extra positional arguments. Use this flexibility only when the function genuinely accepts a variable number of similar values.
**kwargs for Extra Keyword Arguments
def build_profile(name, **details):
profile = {"name": name}
profile.update(details)
return profile
profile = build_profile(
"Ava",
city="Lisbon",
occupation="Developer",
)
print(profile)details is a dictionary. The dedicated *args and **kwargs guide explains unpacking and common mistakes.
Unpack Arguments
def distance(x1, y1, x2, y2):
return ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5
points = (0, 0, 3, 4)
print(distance(*points))For keyword arguments:
options = {"subtotal": 120, "discount": 0.15, "tax": 0.05}
print(calculate_price(**options))Local and Global Scope
Names created inside a function are local unless explicitly declared otherwise.
tax_rate = 0.15
def calculate_tax(price):
subtotal = price
return subtotal * tax_rate
print(calculate_tax(100))
# print(subtotal) # NameError outside the functionPrefer passing values as parameters and returning results instead of changing global state. Pure functions are easier to reason about and test.
Type Hints
Type hints document expected inputs and outputs and help editors and static-analysis tools.
def calculate_total(
price: float,
quantity: int,
discount: float = 0.0,
) -> float:
subtotal = price * quantity
return subtotal * (1 - discount)Python does not enforce these hints automatically at runtime. They improve communication and tooling. The official typing documentation covers the available types.
Docstrings
A docstring explains what a function does, its important parameters, return value, and exceptions.
def divide(a: float, b: float) -> float:
"""Return a divided by b.
Raises:
ValueError: If b is zero.
"""
if b == 0:
raise ValueError("b cannot be zero")
return a / bAccess it through help(divide) or divide.__doc__. Continue with the Python docstrings guide.
Raise Exceptions for Invalid Inputs
A function should reject values that violate its contract.
def calculate_discount(price: float, percentage: float) -> float:
if price < 0:
raise ValueError("price cannot be negative")
if not 0 <= percentage <= 1:
raise ValueError("percentage must be between 0 and 1")
return price * percentageDo not silently replace invalid values unless that behavior is clearly part of the function’s design. The exception-handling guide explains how callers can respond.
Functions as Objects
Functions can be stored in variables, placed in collections, and passed to other functions.
def add(a, b):
return a + b
def multiply(a, b):
return a * b
operations = {
"+": add,
"*": multiply,
}
operator = "*"
print(operations[operator](4, 5))This pattern is useful for terminal menus, event routing, and configurable behavior.
Lambda Expressions
A lambda creates a small anonymous function consisting of one expression.
products = [
{"name": "Mouse", "price": 29.9},
{"name": "Monitor", "price": 249.0},
{"name": "Cable", "price": 8.5},
]
products.sort(key=lambda product: product["price"])
print(products)Use def when logic needs a name, documentation, multiple statements, or error handling. The lambda functions guide compares both forms.
Recursion
A recursive function calls itself and must have a base case.
def factorial(number: int) -> int:
if number < 0:
raise ValueError("number cannot be negative")
if number in {0, 1}:
return 1
return number * factorial(number - 1)Loops are often simpler and avoid recursion-depth limits. Use recursion when the problem is naturally recursive, such as tree traversal. See the Python recursion guide.
Separate Input, Logic, and Output
Keep input() and print() at the application boundary. Core functions should receive values and return results.
def celsius_to_fahrenheit(celsius: float) -> float:
return celsius * 9 / 5 + 32
try:
celsius = float(input("Temperature in Celsius: "))
except ValueError:
print("Enter a valid number.")
else:
fahrenheit = celsius_to_fahrenheit(celsius)
print(f"Temperature: {fahrenheit:.1f} °F")The conversion function can be reused in a web page, API, test, or graphical interface without modification.
Test Functions
def test_calculate_total_without_discount():
assert calculate_total(10, 3) == 30
def test_calculate_total_with_discount():
assert calculate_total(100, 2, 0.10) == 180Small, deterministic functions are easy to test. The Pytest beginner guide explains test discovery, assertions, and exceptions.
Practical Example: Shopping Cart
def line_total(price: float, quantity: int) -> float:
if price < 0 or quantity < 0:
raise ValueError("price and quantity cannot be negative")
return price * quantity
def cart_total(items: list[dict]) -> float:
return sum(
line_total(item["price"], item["quantity"])
for item in items
)
def apply_coupon(total: float, percentage: float = 0.0) -> float:
if not 0 <= percentage <= 1:
raise ValueError("percentage must be between 0 and 1")
return total * (1 - percentage)
cart = [
{"name": "Keyboard", "price": 89.90, "quantity": 1},
{"name": "Cable", "price": 8.50, "quantity": 2},
]
subtotal = cart_total(cart)
final_total = apply_coupon(subtotal, 0.10)
print(f"Final total: ${final_total:.2f}")Each function has one responsibility and can be tested independently.
Common Mistakes
- Printing a result when callers need it returned.
- Creating functions with too many unrelated responsibilities.
- Using vague names such as
do_it()orprocess(). - Changing global variables unnecessarily.
- Using mutable default arguments.
- Catching exceptions inside a function without a recovery plan.
- Adding parameters only to avoid designing a clearer data structure.
- Writing comments instead of extracting meaningful helper functions.
Frequently Asked Questions
What is the difference between a parameter and an argument?
A parameter is the name in the definition; an argument is the value supplied during a call.
Can a function return several values?
Yes. Python packages them into a tuple, which can be unpacked into multiple names.
When should I use a lambda?
Use it for a short expression passed immediately to another function, such as a sorting key. Prefer def for reusable or complex logic.
Are type hints required?
No, but they improve documentation, editor assistance, reviews, and static analysis.
How long should a function be?
There is no strict line limit. It should perform one coherent task and be understandable without excessive scrolling or hidden side effects.
Conclusion
Python functions transform repeated instructions into named, reusable operations. Start with clear parameters and return values, add defaults and keywords where they improve readability, validate the function’s contract, minimize global state, document important behavior, and write tests. These habits make programs easier to extend and debug.






