Functions: The Building Blocks of Reusable Code
Welcome to the world of functions! Think of functions as recipes in a cookbook. Once you write a good recipe, you can use it over and over again.
What are Functions?
Functions are like kitchen appliances:
- Blender: Takes ingredients, mixes them, returns smoothie
- Microwave: Takes food and time, heats it, returns hot food
- Dishwasher: Takes dirty dishes, washes them, returns clean dishes
Each appliance has a specific job and can be used repeatedly!
Why Use Functions?
Real-Life Benefits
- Reusability: Write once, use many times
- Organization: Break complex tasks into smaller pieces
- Maintainability: Fix bugs in one place
- Readability: Code becomes self-documenting
- Testing: Test small pieces individually
Code Without Functions (Bad)
# Calculate area of rectangle
length1 = 5
width1 = 3
area1 = length1 * width1
print(f"Rectangle 1 area: {area1}")
# Calculate area of another rectangle
length2 = 7
width2 = 4
area2 = length2 * width2
print(f"Rectangle 2 area: {area2}")
# Calculate area of a third rectangle
length3 = 10
width3 = 2
area3 = length3 * width3
print(f"Rectangle 3 area: {area3}")
# Lots of repeated code!
Code With Functions (Good)
def calculate_rectangle_area(length, width):
"""Calculate the area of a rectangle."""
return length * width
# Now use the function multiple times
area1 = calculate_rectangle_area(5, 3)
print(f"Rectangle 1 area: {area1}")
area2 = calculate_rectangle_area(7, 4)
print(f"Rectangle 2 area: {area2}")
area3 = calculate_rectangle_area(10, 2)
print(f"Rectangle 3 area: {area3}")
# Much cleaner and reusable!
Defining Functions
Basic Function Structure
def function_name(parameter1, parameter2):
"""
Optional docstring: describes what the function does.
"""
# Function body - the code that runs
result = parameter1 + parameter2
return result # Optional: what the function gives back
Real-Life Example: Coffee Maker Function
def make_coffee(coffee_type, sugar_spoons=0, milk=False):
"""
Make a cup of coffee with specified parameters.
Args:
coffee_type (str): Type of coffee (espresso, latte, etc.)
sugar_spoons (int): Number of sugar spoons (default: 0)
milk (bool): Whether to add milk (default: False)
Returns:
str: Description of the prepared coffee
"""
coffee = f"A cup of {coffee_type}"
if sugar_spoons > 0:
coffee += f" with {sugar_spoons} spoons of sugar"
else:
coffee += " (no sugar)"
if milk:
coffee += " and milk"
else:
coffee += " (black)"
return coffee
# Use the function
my_coffee = make_coffee("espresso", sugar_spoons=2, milk=True)
print(my_coffee) # "A cup of espresso with 2 spoons of sugar and milk"
friends_coffee = make_coffee("latte", milk=True)
print(friends_coffee) # "A cup of latte (no sugar) and milk"
Function Parameters
Required Parameters
def greet_person(name):
"""Greet a person by name."""
return f"Hello, {name}!"
# Must provide the name parameter
greeting = greet_person("Alice")
print(greeting) # "Hello, Alice!"
# greet_person() # Error: missing required argument
Default Parameters
def make_ice_cream(flavor, scoops=1, cone=True):
"""Make ice cream with specified parameters."""
order = f"{scoops} scoop{'s' if scoops > 1 else ''} of {flavor}"
if cone:
order += " in a cone"
else:
order += " in a cup"
return order
# Use with defaults
single_scoop = make_ice_cream("vanilla")
print(single_scoop) # "1 scoop of vanilla in a cone"
# Override defaults
double_cup = make_ice_cream("chocolate", scoops=2, cone=False)
print(double_cup) # "2 scoops of chocolate in a cup"
Variable Number of Arguments (*args)
def make_pizza(*toppings):
"""Make a pizza with any number of toppings."""
if not toppings:
return "Plain cheese pizza"
topping_list = ", ".join(toppings)
return f"Pizza with {topping_list}"
# Different numbers of toppings
plain_pizza = make_pizza()
print(plain_pizza) # "Plain cheese pizza"
pepperoni_pizza = make_pizza("pepperoni")
print(pepperoni_pizza) # "Pizza with pepperoni"
supreme_pizza = make_pizza("pepperoni", "mushrooms", "onions", "bell peppers")
print(supreme_pizza) # "Pizza with pepperoni, mushrooms, onions, bell peppers"
Keyword Arguments (**kwargs)
def build_computer(**specs):
"""Build a computer with specified specifications."""
computer = "Custom computer with:\n"
for component, spec in specs.items():
computer += f" - {component}: {spec}\n"
return computer.strip()
# Build different computers
gaming_pc = build_computer(
CPU="Intel i7",
GPU="RTX 3080",
RAM="32GB",
storage="1TB SSD"
)
print(gaming_pc)
office_pc = build_computer(
CPU="Intel i5",
RAM="16GB",
storage="512GB SSD"
)
print(office_pc)
Return Values
Functions with Return Values
def calculate_discount(price, discount_percent):
"""Calculate discounted price."""
discount_amount = price * (discount_percent / 100)
final_price = price - discount_amount
return final_price
# Use the returned value
original_price = 100
sale_price = calculate_discount(original_price, 20)
print(f"Original: ${original_price}, Sale: ${sale_price}") # "Original: $100, Sale: $80"
Functions Without Return Values
def print_welcome_message(name):
"""Print a welcome message (no return value)."""
print(f"Welcome to our store, {name}!")
print("We hope you find everything you need.")
# Function returns None implicitly
result = print_welcome_message("Alice")
print(f"Function returned: {result}") # "Function returned: None"
Multiple Return Values
def analyze_numbers(numbers):
"""Analyze a list of numbers."""
if not numbers:
return None, None, None
total = sum(numbers)
average = total / len(numbers)
maximum = max(numbers)
return total, average, maximum
# Unpack multiple return values
nums = [10, 20, 30, 40, 50]
total, avg, max_num = analyze_numbers(nums)
print(f"Total: {total}, Average: {avg:.2f}, Maximum: {max_num}")
# "Total: 150, Average: 30.00, Maximum: 50"
Scope and Variables
Local vs Global Variables
global_variable = "I am global"
def demonstrate_scope():
local_variable = "I am local"
print(f"Inside function: {global_variable}") # Can access global
print(f"Inside function: {local_variable}") # Can access local
# Modify global variable (need to declare global)
global global_variable
global_variable = "I was modified inside function"
demonstrate_scope()
print(f"Outside function: {global_variable}") # Modified global
# print(local_variable) # Error: local_variable not defined outside function
Best Practice: Avoid Global Variables
# ❌ Bad: Using global variables
total_purchases = 0
def add_purchase(amount):
global total_purchases
total_purchases += amount
# ✅ Good: Pass parameters and return values
def add_purchase(current_total, amount):
return current_total + amount
# Usage
total = 0
total = add_purchase(total, 50)
total = add_purchase(total, 30)
print(f"Total purchases: ${total}")
Function Examples
Example 1: Temperature Converter
def celsius_to_fahrenheit(celsius):
"""Convert Celsius to Fahrenheit."""
return (celsius * 9/5) + 32
def fahrenheit_to_celsius(fahrenheit):
"""Convert Fahrenheit to Celsius."""
return (fahrenheit - 32) * 5/9
# Use the functions
temp_c = 25
temp_f = celsius_to_fahrenheit(temp_c)
print(f"{temp_c}°C = {temp_f}°F")
temp_f2 = 77
temp_c2 = fahrenheit_to_celsius(temp_f2)
print(f"{temp_f2}°F = {temp_c2:.1f}°C")
Example 2: Simple Calculator
def add(x, y):
return x + y
def subtract(x, y):
return x - y
def multiply(x, y):
return x * y
def divide(x, y):
if y == 0:
return "Error: Division by zero!"
return x / y
# Calculator function
def calculator(operation, x, y):
"""Perform calculation based on operation."""
if operation == "add":
return add(x, y)
elif operation == "subtract":
return subtract(x, y)
elif operation == "multiply":
return multiply(x, y)
elif operation == "divide":
return divide(x, y)
else:
return "Error: Unknown operation!"
# Use the calculator
result1 = calculator("add", 10, 5)
print(f"10 + 5 = {result1}")
result2 = calculator("divide", 10, 0)
print(result2) # "Error: Division by zero!"
Example 3: Password Validator
def validate_password(password):
"""
Validate password strength.
Returns (is_valid, message)
"""
if len(password) < 8:
return False, "Password must be at least 8 characters long"
has_upper = any(char.isupper() for char in password)
has_lower = any(char.islower() for char in password)
has_digit = any(char.isdigit() for char in password)
if not has_upper:
return False, "Password must contain at least one uppercase letter"
if not has_lower:
return False, "Password must contain at least one lowercase letter"
if not has_digit:
return False, "Password must contain at least one digit"
return True, "Password is strong!"
# Test passwords
passwords = ["weak", "Password", "password123", "Password123"]
for pwd in passwords:
is_valid, message = validate_password(pwd)
status = "✅" if is_valid else "❌"
print(f"{status} {pwd}: {message}")
Lambda Functions (Anonymous Functions)
Simple Lambda Functions
# Regular function
def square(x):
return x ** 2
# Lambda equivalent
square_lambda = lambda x: x ** 2
print(square(5)) # 25
print(square_lambda(5)) # 25
Lambda with Multiple Parameters
# Regular function
def add(x, y):
return x + y
# Lambda equivalent
add_lambda = lambda x, y: x + y
print(add(3, 4)) # 7
print(add_lambda(3, 4)) # 7
Common Lambda Use Cases
# Sort list of tuples by second element
students = [("Alice", 85), ("Bob", 92), ("Charlie", 78)]
students.sort(key=lambda student: student[1]) # Sort by grade
print(students) # [('Charlie', 78), ('Alice', 85), ('Bob', 92)]
# Filter even numbers
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers) # [2, 4, 6, 8, 10]
# Transform list elements
names = ["alice", "bob", "charlie"]
capitalized = list(map(lambda name: name.capitalize(), names))
print(capitalized) # ['Alice', 'Bob', 'Charlie']
Function Best Practices
1. Single Responsibility Principle
# ❌ Bad: Function does too many things
def process_user_data(data):
# Validate data
# Save to database
# Send email
# Log activity
pass
# ✅ Good: Each function has one responsibility
def validate_user_data(data):
pass
def save_user_to_database(user):
pass
def send_welcome_email(user):
pass
def log_user_activity(user, action):
pass
2. Descriptive Names
# ❌ Bad names
def f(x, y): # What does f do?
return x * y
def calc(a, b, c): # What calculation?
return (a + b) * c
# ✅ Good names
def calculate_rectangle_area(length, width):
return length * width
def calculate_discounted_price(original_price, discount_rate):
return original_price * (1 - discount_rate)
3. Docstrings
# ❌ No documentation
def calculate_tax(amount):
return amount * 0.08
# ✅ With docstring
def calculate_tax(amount, tax_rate=0.08):
"""
Calculate tax amount for a given amount.
Args:
amount (float): The amount to calculate tax for
tax_rate (float): Tax rate as decimal (default: 0.08 for 8%)
Returns:
float: The calculated tax amount
Examples:
>>> calculate_tax(100)
8.0
>>> calculate_tax(100, 0.10)
10.0
"""
return amount * tax_rate
4. Error Handling
# ❌ No error handling
def divide_numbers(x, y):
return x / y
# result = divide_numbers(10, 0) # Crash!
# ✅ With error handling
def safe_divide(x, y):
"""
Safely divide two numbers.
Args:
x (float): Numerator
y (float): Denominator
Returns:
float or None: Result of division, or None if division by zero
"""
try:
return x / y
except ZeroDivisionError:
print("Error: Cannot divide by zero!")
return None
result = safe_divide(10, 0) # Returns None, no crash
Practice Exercises
Exercise 1: Unit Converter
Create functions to convert between different units:
- Celsius ↔ Fahrenheit
- Kilometers ↔ Miles
- Kilograms ↔ Pounds
Exercise 2: Shopping Cart Functions
Create functions for a shopping cart:
- Add item to cart
- Remove item from cart
- Calculate total price
- Apply discount
Exercise 3: Text Processing Functions
Create functions for text processing:
- Count words in a string
- Check if string is palindrome
- Capitalize first letter of each word
- Remove punctuation
Exercise 4: Math Functions
Create mathematical functions:
- Factorial calculation
- Check if number is prime
- Generate Fibonacci sequence
- Calculate compound interest
Exercise 5: File Operations (Preview)
Create functions for file operations:
- Read text file
- Write to text file
- Count lines in file
- Search for word in file
Summary
Functions are the building blocks of organized, reusable code:
- Define functions with
def function_name(parameters): - Parameters: Required, default, *args, **kwargs
- Return values: Use
returnto send results back - Scope: Local variables are only accessible inside functions
- Lambda functions: Anonymous functions for simple operations
Benefits of functions:
- Reusability: Write once, use many times
- Organization: Break complex problems into smaller pieces
- Maintainability: Easier to debug and update
- Readability: Self-documenting code
- Testing: Test individual components
Next: Data Structures - organizing data efficiently! 📊