Functions
Chapter 3: Functions
Functions are reusable blocks of code that perform specific tasks. Instead of writing the same code repeatedly, you define it once in a function and call it whenever needed. For example, if you need to calculate tax in multiple places, write a function once and reuse it everywhere. Functions make programs easier to understand, test, and maintain by breaking complex logic into manageable pieces with clear purposes.
Every program you write will use functions extensively. Python provides many built-in functions like print(), len(), and range(), but the real power comes from writing your own. Custom functions let you organize code logically, avoid repetition, and create abstractions that hide complexity behind simple interfaces.
Defining Functions with def
Create functions using the def keyword, followed by the function name, parentheses, and a colon. The function body contains indented code that runs when you call the function:
Function names follow the same rules as variables: use lowercase with underscores for multi-word names (calculate_total, get_user_input). Choose names that clearly describe what the function does—reading send_email() immediately tells you its purpose.
The parentheses after the function name are required even when the function takes no parameters. They signal to Python that you're defining or calling a function, not working with a variable.
Parameters: Passing Data to Functions
Functions become much more powerful when they accept input. Parameters let you pass data into functions, making them flexible and reusable for different situations:
The parameter name acts as a variable inside the function, holding whatever value you pass when calling it. You can define multiple parameters separated by commas:
When calling functions with multiple parameters, order matters. The first argument goes to the first parameter, the second to the second parameter, and so on. You can also use keyword arguments to specify parameters by name, making calls clearer:
calculate_area(width=5, height=10) # Explicit and clear
calculate_area(height=10, width=5) # Order doesn't matter with keyword argsReturn Values: Getting Results Back
Functions can send results back to the caller using the return statement. This lets you use function results in calculations, assignments, or as arguments to other functions:
def add_numbers(a, b):
result = a + b
return result
total = add_numbers(5, 3)
print(f"Total: {total}") # Output: 8
# Use directly in expressions
doubled = add_numbers(4, 6) * 2
print(f"Doubled: {doubled}") # Output: 20Once Python executes a return statement, the function immediately exits. Code after return doesn't run:
def check_positive(number):
if number > 0:
return "Positive"
return "Not positive"
print(check_positive(5)) # Output: Positive
print(check_positive(-3)) # Output: Not positiveFunctions without explicit return statements automatically return None. This is fine for functions that perform actions (like print()) rather than calculate values.
Default Parameter Values
Sometimes parameters have sensible defaults. Python lets you specify default values, making those parameters optional when calling the function:
def greet_with_time(name, time_of_day="day"):
print(f"Good {time_of_day}, {name}!")
greet_with_time("Alice") # Uses default: Good day, Alice!
greet_with_time("Bob", "morning") # Overrides: Good morning, Bob!
greet_with_time("Carol", "evening") # Overrides: Good evening, Carol!Default parameters must come after required parameters in the function definition. This makes sense—Python needs to know which arguments match which parameters:
# Correct: required first, defaults after
def create_user(username, role="member", active=True):
print(f"User: {username}, Role: {role}, Active: {active}")
create_user("alice") # Uses both defaults
create_user("bob", "admin") # Overrides role
create_user("carol", "admin", False) # Overrides both
create_user("dave", active=False) # Skip role, set activeThis pattern appears constantly in real code. Libraries provide sensible defaults while letting you customize behavior when needed.
Variable Scope: Local vs Global
Variables created inside functions are local—they exist only while the function runs and can't be accessed from outside:
def calculate_discount(price):
discount_rate = 0.1 # Local variable
discount = price * discount_rate
return discount
result = calculate_discount(100)
print(result) # Output: 10.0
# print(discount_rate) # Error: discount_rate doesn't exist hereVariables defined outside functions are global—accessible everywhere, including inside functions:
tax_rate = 0.08 # Global variable
def calculate_tax(price):
tax = price * tax_rate # Can read global tax_rate
return tax
print(calculate_tax(100)) # Output: 8.0Be careful modifying global variables from inside functions. It's better to pass values as parameters and return results. This makes functions predictable and easier to test:
# Good: explicit parameters and return
def apply_discount(price, discount_rate):
return price * (1 - discount_rate)
# Better than modifying globals
discounted_price = apply_discount(100, 0.1)Practical Function Patterns
Functions shine when you identify repeated logic and extract it. Consider calculating shipping costs:
def calculate_shipping(weight, distance):
base_rate = 2.0
weight_cost = weight * 0.5
distance_cost = distance * 0.1
return base_rate + weight_cost + distance_cost
# Use for different orders
order1_shipping = calculate_shipping(5, 100) # 5kg, 100km
order2_shipping = calculate_shipping(2, 50) # 2kg, 50km
order3_shipping = calculate_shipping(10, 200) # 10kg, 200kmFunctions also improve readability by hiding complexity behind descriptive names:
def is_valid_email(email):
return "@" in email and "." in email
def is_valid_password(password):
return len(password) >= 8
# Clear, readable validation
if is_valid_email(user_email) and is_valid_password(user_password):
print("Valid credentials!")Breaking programs into small, focused functions makes code easier to understand, modify, and debug. Each function does one thing well, and complex operations emerge from combining simple functions.
Try It Yourself: Practice Exercises
Exercise 1: Temperature Converter
Write a function celsius_to_fahrenheit(celsius) that converts Celsius to Fahrenheit using the formula: (celsius * 9/5) + 32. Test it with several values.
Exercise 2: Even or Odd Checker
Create a function is_even(number) that returns True if the number is even, False otherwise. Use the modulo operator %.
Exercise 3: Greeting Generator
Write a function create_greeting(name, greeting="Hello") with a default greeting. Call it with one argument, then with two.
Exercise 4: Max of Three
Create a function max_of_three(a, b, c) that returns the largest of three numbers. Don't use Python's built-in max() function—use if statements instead.
Exercise 5: List Sum
Write a function sum_list(numbers) that takes a list of numbers and returns their sum. Don't use the built-in sum() function—use a loop.
Exercise 6: Password Validator
Create a function is_strong_password(password) that returns True if the password is at least 8 characters and contains at least one number. Use a loop to check for digits.
These exercises reinforce function fundamentals. Experiment with different inputs and edge cases to build confidence.
What's Next?
You've mastered Python functions: defining them with def, accepting parameters, returning values, using defaults, and understanding scope. Functions are fundamental to every Python program you'll write, enabling code reuse and logical organization.
In the next chapter, we'll explore data structures—lists, tuples, dictionaries, and sets. These containers let you organize and manipulate collections of data efficiently. Combined with functions, data structures form the backbone of practical Python programming.
📝 Test Your Knowledge: Functions
Take this quiz to reinforce what you've learned in this chapter.