Lesson 88 min read

Functions

Write it once, use it forever — the ultimate code recycling

What Are Functions?

A function is like a recipe card. You write the instructions once, give it a name, and whenever you need that dish, you just call the recipe by name. You can even customize it — "make this recipe but with extra cheese" (that's a parameter!).

Functions help you:

  • Avoid repetition — Write code once, call it many times.
  • Organize — Break complex programs into manageable pieces.
  • Test — Easier to test one small function than a massive script.

Defining & Calling Functions

# Define a function with 'def'
def greet(name):
"""Say hello to someone."""
print(f"Hello, {name}! Welcome aboard.")
# Call the function
greet("Alice")
greet("Bob")
# Function with a return value
def calculate_tip(bill, tip_percent=18):
"""Calculate tip amount. Default is 18%."""
tip = bill * (tip_percent / 100)
return round(tip, 2)
result = calculate_tip(50) # Uses default 18%
print(f"Tip on $50: ${result}")
result = calculate_tip(50, 20) # Custom 20%
print(f"Generous tip: ${result}")
Output
Hello, Alice! Welcome aboard.
Hello, Bob! Welcome aboard.
Tip on $50: $9.0
Generous tip: $10.0

Parameters Deep Dive

Python gives you incredibly flexible ways to pass data into functions:

  • Positional args — matched by position: greet("Alice", 25)
  • Keyword args — matched by name: greet(name="Alice", age=25)
  • Default values — optional parameters with fallbacks: def greet(name, greeting="Hello")
  • *args — Collects extra positional arguments into a tuple
  • **kwargs — Collects extra keyword arguments into a dictionary

*args and **kwargs

# *args — accept any number of positional arguments
def total(*prices):
"""Sum up any number of prices."""
print(f"Items: {prices}") # It's a tuple!
return sum(prices)
print(f"Total: ${total(5.99, 3.49, 12.00)}")
print()
# **kwargs — accept any number of keyword arguments
def build_profile(**info):
"""Build a user profile from keyword arguments."""
print(f"Profile: {info}") # It's a dict!
for key, value in info.items():
print(f" {key}: {value}")
build_profile(name="Luna", age=12, hobby="coding")
print()
# Combining all parameter types
def order(main, *sides, drink="water", **extras):
print(f"Main: {main}")
print(f"Sides: {sides}")
print(f"Drink: {drink}")
print(f"Extras: {extras}")
order("burger", "fries", "salad", drink="soda", ketchup=True)
Output
Items: (5.99, 3.49, 12.0)
Total: $21.48

Profile: {'name': 'Luna', 'age': 12, 'hobby': 'coding'}
  name: Luna
  age: 12
  hobby: coding

Main: burger
Sides: ('fries', 'salad')
Drink: soda
Extras: {'ketchup': True}

Scope — Where Variables Live

Variables created inside a function are local — they live on the call stack and exist only while the function is running, then vanish. Variables created outside functions are global — they stick around for the whole program.

Think of it like this: local variables are written on a whiteboard in a meeting room. When the meeting's over, the whiteboard gets erased. Global variables are written on the office wall — everyone can see them, but changing them from inside a meeting room requires special permission (global keyword).

Scope & Returning Multiple Values

# Scope demo
name = "Global Alice" # global variable
def change_name():
name = "Local Bob" # this is a DIFFERENT variable!
print(f"Inside function: {name}")
change_name()
print(f"Outside function: {name}") # unchanged!
print()
# Returning multiple values (actually a tuple)
def min_max(numbers):
"""Return both the minimum and maximum."""
return min(numbers), max(numbers)
lowest, highest = min_max([42, 17, 93, 5, 68])
print(f"Min: {lowest}, Max: {highest}")
# Returning a dictionary for named results
def analyze_text(text):
words = text.split()
return {
"word_count": len(words),
"char_count": len(text),
"avg_word_length": round(sum(len(w) for w in words) / len(words), 1)
}
stats = analyze_text("The quick brown fox jumps")
print(stats)
Output
Inside function: Local Bob
Outside function: Global Alice

Min: 5, Max: 93
{'word_count': 5, 'char_count': 25, 'avg_word_length': 4.2}

Docstrings — Document Your Functions

A docstring is a triple-quoted string right after the def line. It describes what the function does, what it takes, and what it returns. It's not just a comment — Python actually stores it and you can access it with help().

Docstrings & Best Practices

def bmi(weight_kg, height_m):
"""
Calculate Body Mass Index.
Args:
weight_kg: Weight in kilograms
height_m: Height in meters
Returns:
BMI as a float, rounded to 1 decimal place
"""
return round(weight_kg / (height_m ** 2), 1)
# Access the docstring
print(bmi.__doc__)
# Use the function
print(f"BMI: {bmi(70, 1.75)}")
Output

    Calculate Body Mass Index.

    Args:
        weight_kg: Weight in kilograms
        height_m: Height in meters

    Returns:
        BMI as a float, rounded to 1 decimal place
    
BMI: 22.9
Note: A good function does one thing well. If you're struggling to name your function, it might be doing too much. "calculate_and_print_and_save_results" should probably be three separate functions!

Quick check

What does a function return if it has no return statement?
Dictionaries & SetsComprehensions