Chapter 9: Functions: Reusable Code 🔧

Imagine you need to calculate the area of a rectangle in your program 10 different times. You could write length * width every single time, but what if you make a mistake in one place? What if the formula needs to change?

Functions solve this problem. They let you write code once and reuse it as many times as you need. You’ve already been using built-in functions like print(), len(), and input(). Now it’s time to create your own!

By the end of this chapter, you’ll be able to write cleaner, more organized code by breaking complex programs into smaller, reusable pieces.


What are Functions? 🎯

A function is a named block of code that performs a specific task. Think of it as a mini-program within your program. You define it once, then “call” (use) it whenever you need it.

Why Use Functions?

Functions provide several benefits:

1. Avoid Repetition (DRY - Don’t Repeat Yourself)

# Without functions - repetitive
print("=" * 40)
print("Welcome to the Game!")
print("=" * 40)

# Later in the program...
print("=" * 40)
print("Welcome to the Game!")
print("=" * 40)

# And again...
print("=" * 40)
print("Welcome to the Game!")
print("=" * 40)
# With functions - clean and reusable
def show_header():
    print("=" * 40)
    print("Welcome to the Game!")
    print("=" * 40)

show_header()  # Use it once
# ... other code ...
show_header()  # Use it again
# ... other code ...
show_header()  # And again

2. Organize Your Code

Functions help you break large programs into logical pieces, making them easier to understand and maintain.

3. Fix Bugs in One Place

If there’s an error in your code, you only need to fix it once in the function, not everywhere you used it.

4. Make Code Readable

Well-named functions make your code self-documenting:

calculate_total_price(items, tax_rate)  # Clear what this does
validate_email(user_input)              # Obvious purpose
send_welcome_message(username)          # Easy to understand

Defining and Calling Functions 📝

Creating a function has two steps: defining it (writing what it does) and calling it (using it).

Basic Syntax

def function_name():
    # Code to execute
    print("This is inside the function")

The structure:

  1. Start with def (short for “define”)
  2. Give it a name (follow variable naming rules)
  3. Add parentheses ()
  4. End with a colon :
  5. Indent the code that belongs to the function

A Simple Example

# Define the function
def say_hello():
    print("Hello!")
    print("How are you today?")

# Call the function
say_hello()

Output:

Hello!
How are you today?

Calling Multiple Times

Once defined, you can call a function as many times as you want:

def show_message():
    print("Python is awesome!")

show_message()
show_message()
show_message()

Output:

Python is awesome!
Python is awesome!
Python is awesome!

Functions Don’t Run Until Called

Important: defining a function doesn’t execute its code. The code only runs when you call it:

def greet():
    print("Hello!")

# Nothing happens yet - just defined
print("Starting program")  # This prints

greet()  # NOW the function runs

print("Ending program")  # This prints too

Output:

Starting program
Hello!
Ending program

Practical Example

def display_menu():
    print("\n=== Main Menu ===")
    print("1. New Game")
    print("2. Load Game")
    print("3. Settings")
    print("4. Exit")
    print("=" * 17)

# Use the function
display_menu()
choice = input("Choose an option: ")

if choice == "1":
    print("Starting new game...")
    # Later you might show the menu again
    display_menu()

Function Parameters 📥

Functions become much more powerful when they can accept parameters (also called arguments) — values you pass in to customize what the function does.

Basic Parameters

def greet(name):
    print(f"Hello, {name}!")
    print("Welcome to our program!")

greet("Alice")   # Shows: Hello, Alice!
greet("Bob")     # Shows: Hello, Bob!
greet("Charlie") # Shows: Hello, Charlie!

The name inside the parentheses is a parameter — a variable that receives the value you pass in.

How Parameters Work

When you call the function, the value you provide is assigned to the parameter:

def welcome(username):
    # 'username' becomes "Alex" when we call welcome("Alex")
    print(f"Welcome, {username}!")

welcome("Alex")  # username = "Alex"

Multiple Parameters

Functions can accept multiple parameters, separated by commas:

def introduce(name, age, hobby):
    print(f"Hi! My name is {name}.")
    print(f"I'm {age} years old.")
    print(f"I love {hobby}!")

introduce("Jordan", 14, "gaming")

Output:

Hi! My name is Jordan.
I'm 14 years old.
I love gaming!

The order matters — the first value goes to the first parameter, second to second, etc.

Practical Examples

Example 1: Calculate rectangle area

def calculate_area(length, width):
    area = length * width
    print(f"A rectangle {length} x {width} has area: {area}")

calculate_area(5, 3)   # Shows: 15
calculate_area(10, 7)  # Shows: 70
calculate_area(4, 4)   # Shows: 16

Example 2: Display formatted message

def show_score(player_name, points, level):
    print("=" * 40)
    print(f"Player: {player_name}")
    print(f"Score: {points}")
    print(f"Level: {level}")
    print("=" * 40)

show_score("DragonSlayer", 1500, 8)
show_score("CodeMaster", 2300, 12)

Example 3: Personalized greeting

def greet_user(name, time_of_day):
    print(f"Good {time_of_day}, {name}!")
    print("How can I help you today?")

greet_user("Alice", "morning")
greet_user("Bob", "afternoon")
greet_user("Charlie", "evening")

Default Parameter Values

You can give parameters default values that are used if no argument is provided:

def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

greet("Alice")              # Shows: Hello, Alice!
greet("Bob", "Hi")          # Shows: Hi, Bob!
greet("Charlie", "Welcome") # Shows: Welcome, Charlie!

Default parameters must come after non-default ones:

# Correct
def create_user(username, role="user", active=True):
    print(f"Created {username} as {role}, Active: {active}")

# Wrong - will cause an error
# def create_user(username="guest", role, active=True):

Return Values 🔄

So far, our functions have printed output. But often you want a function to calculate something and give the result back to use elsewhere. That’s what the return statement does.

Basic Return

def add(a, b):
    result = a + b
    return result

total = add(5, 3)
print(total)  # Shows: 8

The return statement:

  1. Sends a value back to where the function was called
  2. Immediately exits the function

Using Return Values

Once a function returns a value, you can use it like any other value:

def multiply(x, y):
    return x * y

# Use in a variable
product = multiply(4, 7)
print(product)  # Shows: 28

# Use in calculations
result = multiply(3, 5) + multiply(2, 4)
print(result)  # Shows: 15 + 8 = 23

# Use in conditions
if multiply(2, 3) > 5:
    print("Greater than 5!")

# Use in f-strings
print(f"4 times 6 is {multiply(4, 6)}")

Return vs Print

This is a common confusion for beginners:

# This PRINTS - you see output but can't use the value
def add_and_print(a, b):
    result = a + b
    print(result)

add_and_print(5, 3)  # Shows: 8
# But you can't do: total = add_and_print(5, 3) + 10

# This RETURNS - you get the value to use
def add_and_return(a, b):
    result = a + b
    return result

total = add_and_return(5, 3) + 10
print(total)  # Shows: 18

Use return when you want to use the result. Use print() when you just want to show information.

Returning Multiple Values

Python lets you return multiple values as a tuple:

def get_stats(numbers):
    total = sum(numbers)
    count = len(numbers)
    average = total / count
    return total, count, average

scores = [85, 92, 78, 95, 88]
sum_score, num_scores, avg_score = get_stats(scores)

print(f"Total: {sum_score}")
print(f"Count: {num_scores}")
print(f"Average: {avg_score:.1f}")

Early Return

You can use return to exit a function early:

def divide(a, b):
    if b == 0:
        print("Error: Cannot divide by zero!")
        return None  # Exit early
    
    return a / b

result = divide(10, 2)
print(result)  # Shows: 5.0

result = divide(10, 0)
print(result)  # Shows: None

Practical Examples

Example 1: Temperature converter

def celsius_to_fahrenheit(celsius):
    fahrenheit = (celsius * 9/5) + 32
    return fahrenheit

temp_c = 25
temp_f = celsius_to_fahrenheit(temp_c)
print(f"{temp_c}°C = {temp_f}°F")

Example 2: Price calculator

def calculate_total(price, quantity, tax_rate=0.08):
    subtotal = price * quantity
    tax = subtotal * tax_rate
    total = subtotal + tax
    return total

item_price = 19.99
item_qty = 3
final_price = calculate_total(item_price, item_qty)
print(f"Total: ${final_price:.2f}")

Example 3: Grade calculator

def calculate_grade(score):
    if score >= 90:
        return "A"
    elif score >= 80:
        return "B"
    elif score >= 70:
        return "C"
    elif score >= 60:
        return "D"
    else:
        return "F"

student_score = 87
grade = calculate_grade(student_score)
print(f"Score {student_score} = Grade {grade}")

Scope: Local vs Global Variables 🌍

Variables have scope — where they can be accessed in your code. Understanding scope helps you avoid bugs and write better functions.

Local Scope

Variables created inside a function only exist within that function:

def my_function():
    message = "Hello from inside!"  # Local variable
    print(message)

my_function()
# print(message)  # ERROR: message doesn't exist here

Global Scope

Variables created outside functions are global — accessible everywhere:

game_title = "Space Adventure"  # Global variable

def show_title():
    print(game_title)  # Can access global variable

show_title()  # Shows: Space Adventure
print(game_title)  # Shows: Space Adventure

Parameters Are Local

Function parameters are local variables:

def greet(name):  # 'name' is local
    print(f"Hello, {name}!")

greet("Alice")
# print(name)  # ERROR: name doesn't exist outside function

Best Practice: Avoid Global Variables

It’s better to pass values as parameters and return results:

# Less ideal - using global variable
total_score = 0

def add_points(points):
    global total_score  # Needed to modify global
    total_score += points

# Better - using parameters and return
def add_points(current_score, points):
    return current_score + points

total_score = 0
total_score = add_points(total_score, 100)

The second approach is clearer and less error-prone.


Function Examples and Patterns 💡

Let’s look at common patterns and practical function examples.

Pattern 1: Validation Functions

def is_valid_age(age):
    return 0 < age < 120

def is_valid_email(email):
    return "@" in email and "." in email

# Use them
user_age = int(input("Enter your age: "))
if is_valid_age(user_age):
    print("Valid age!")
else:
    print("Invalid age!")

Pattern 2: Formatting Functions

def format_currency(amount):
    return f"${amount:.2f}"

def format_percentage(value):
    return f"{value * 100:.1f}%"

price = 49.99
discount = 0.15

print(f"Price: {format_currency(price)}")
print(f"Discount: {format_percentage(discount)}")

Pattern 3: Menu Display Functions

def show_main_menu():
    print("\n" + "=" * 30)
    print("       MAIN MENU")
    print("=" * 30)
    print("1. Start Game")
    print("2. Settings")
    print("3. Quit")
    print("=" * 30)

def get_menu_choice(min_option, max_option):
    while True:
        choice = input(f"Enter choice ({min_option}-{max_option}): ")
        if choice.isdigit():
            num = int(choice)
            if min_option <= num <= max_option:
                return num
        print("Invalid choice. Try again.")

Pattern 4: Calculation Functions

def calculate_bmi(weight_kg, height_m):
    bmi = weight_kg / (height_m ** 2)
    return round(bmi, 1)

def get_bmi_category(bmi):
    if bmi < 18.5:
        return "Underweight"
    elif bmi < 25:
        return "Normal"
    elif bmi < 30:
        return "Overweight"
    else:
        return "Obese"

# Use together
weight = 70
height = 1.75
bmi_value = calculate_bmi(weight, height)
category = get_bmi_category(bmi_value)

print(f"BMI: {bmi_value} ({category})")

Pattern 5: List Processing Functions

def find_highest(numbers):
    if len(numbers) == 0:
        return None
    highest = numbers[0]
    for num in numbers:
        if num > highest:
            highest = num
    return highest

def calculate_average(numbers):
    if len(numbers) == 0:
        return 0
    return sum(numbers) / len(numbers)

scores = [85, 92, 78, 95, 88]
print(f"Highest: {find_highest(scores)}")
print(f"Average: {calculate_average(scores):.1f}")

Quick Recap 🎯

You’ve learned how to create and use functions:

  • Define functions with def and a name
  • Call functions to execute their code
  • Pass parameters to customize function behavior
  • Return values to get results from functions
  • Understand scope — local vs global variables
  • Use common patterns for validation, formatting, and calculations

Functions make your code reusable, organized, and easier to maintain!


Hands-On Mini Project: Calculator Program 🧮

Let’s build a comprehensive calculator using functions to organize different operations.

Your Mission

Create a calculator program that:

  1. Has separate functions for each operation
  2. Displays a menu of operations
  3. Gets user input and performs calculations
  4. Shows results in a formatted way
  5. Lets users perform multiple calculations

Example Solution

# Calculator Program with Functions

def add(a, b):
    """Add two numbers and return the result."""
    return a + b

def subtract(a, b):
    """Subtract b from a and return the result."""
    return a - b

def multiply(a, b):
    """Multiply two numbers and return the result."""
    return a * b

def divide(a, b):
    """Divide a by b and return the result."""
    if b == 0:
        return None  # Cannot divide by zero
    return a / b

def power(a, b):
    """Raise a to the power of b."""
    return a ** b

def percentage(value, percent):
    """Calculate what percentage of value the percent represents."""
    return (value * percent) / 100

def display_menu():
    """Display the calculator menu."""
    print("\n" + "=" * 50)
    print("           PYTHON CALCULATOR")
    print("=" * 50)
    print("1. Add")
    print("2. Subtract")
    print("3. Multiply")
    print("4. Divide")
    print("5. Power")
    print("6. Percentage")
    print("7. Exit")
    print("=" * 50)

def get_number(prompt):
    """Get a number from the user with validation."""
    while True:
        try:
            return float(input(prompt))
        except ValueError:
            print("Invalid input. Please enter a number.")

def format_result(operation, num1, num2, result):
    """Format and display the result."""
    print("\n" + "-" * 50)
    if result is None:
        print("ERROR: Cannot divide by zero!")
    else:
        print(f"Result: {result}")
        print(f"Calculation: {num1} {operation} {num2} = {result}")
    print("-" * 50)

# Main program
def main():
    """Main calculator program loop."""
    print("Welcome to the Python Calculator!")
    
    running = True
    
    while running:
        display_menu()
        choice = input("\nChoose an operation (1-7): ")
        
        if choice == "7":
            print("\nThank you for using Python Calculator!")
            print("Goodbye! 👋\n")
            running = False
            continue
        
        if choice not in ["1", "2", "3", "4", "5", "6"]:
            print("Invalid choice. Please select 1-7.")
            continue
        
        # Get numbers from user
        num1 = get_number("Enter first number: ")
        num2 = get_number("Enter second number: ")
        
        # Perform calculation based on choice
        if choice == "1":
            result = add(num1, num2)
            format_result("+", num1, num2, result)
        
        elif choice == "2":
            result = subtract(num1, num2)
            format_result("-", num1, num2, result)
        
        elif choice == "3":
            result = multiply(num1, num2)
            format_result("×", num1, num2, result)
        
        elif choice == "4":
            result = divide(num1, num2)
            format_result("÷", num1, num2, result)
        
        elif choice == "5":
            result = power(num1, num2)
            format_result("^", num1, num2, result)
        
        elif choice == "6":
            result = percentage(num1, num2)
            print("\n" + "-" * 50)
            print(f"{num2}% of {num1} = {result}")
            print("-" * 50)

# Run the calculator
main()

Challenge Yourself

Enhance your calculator with these features:

  1. More operations — Square root, absolute value, rounding
  2. Memory functions — Store and recall previous results
  3. History — Keep track of all calculations
  4. Scientific mode — Add trigonometric functions (sin, cos, tan)
  5. Unit converter — Convert between units (km/miles, kg/lbs, etc.)
  6. Expression parser — Let users type “5 + 3 * 2” instead of choosing operations
  7. Decimal precision — Let users choose how many decimal places to display
  8. Clear screen — Clear the terminal between operations
  9. Error messages — More detailed error handling
  10. Statistics — Calculate mean, median, mode from a list of numbers

Tips for Building

  • Keep each function focused on one task
  • Use descriptive function names
  • Add comments explaining what each function does
  • Validate user input before performing operations
  • Test edge cases (dividing by zero, negative numbers, etc.)
  • Consider using docstrings (the """text""" format) to document functions

What’s Next?

In the next chapter, we’ll put everything together! You’ll learn about the random module, debugging basics, and create a Capstone Project: Text Adventure Game that combines all the skills you’ve learned throughout this series.

Get ready for the final challenge!


Fantastic work! Functions are essential for writing professional, maintainable code!