Functions: Reusable, Named Code Blocks
Functions are the foundation of maintainable code. They let you name a chunk of logic, reuse it across your program, and test it in isolation. Good programmers write small, focused functions that do one thing well.
def greet(name):
"""Say hello to someone.""" # Docstring โ always document!
return f"Hello, {name}!"
# Call the function
message = greet("Alice")
print(message) # Hello, Alice!Parameters and Arguments
# Positional arguments โ order matters
def add(a, b):
return a + b
add(3, 5) # 8
add(5, 3) # 8 โ same here, but for subtract(5,3) != subtract(3,5)
# Default parameters โ provide fallback values
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
greet("Alice") # "Hello, Alice!"
greet("Bob", "Good morning") # "Good morning, Bob!"
greet(greeting="Hi", name="Carol") # Keyword arguments โ order-independent
# Important: default values are evaluated ONCE at definition
# Never use mutable defaults like lists!
def bad(items=[]): # BUG: same list shared across calls!
items.append(1)
return items
def good(items=None): # CORRECT: create fresh list each time
if items is None:
items = []
items.append(1)
return items*args and **kwargs
# *args โ collect extra positional args as a TUPLE
def add_all(*numbers):
return sum(numbers)
add_all(1, 2, 3) # 6
add_all(1, 2, 3, 4, 5) # 15
add_all() # 0
# **kwargs โ collect extra keyword args as a DICT
def print_info(**details):
for key, value in details.items():
print(f"{key}: {value}")
print_info(name="Alice", age=30, city="NYC")
# Combining all parameter types (must be in this order)
def combined(required, *args, keyword_only="default", **kwargs):
print(required, args, keyword_only, kwargs)
combined("must", 1, 2, 3, keyword_only="x", extra="y")
# must (1, 2, 3) x {'extra': 'y'}
# Unpacking into function calls
def add(a, b, c):
return a + b + c
nums = [1, 2, 3]
add(*nums) # Same as add(1, 2, 3)
data = {"a": 1, "b": 2, "c": 3}
add(**data) # Same as add(a=1, b=2, c=3)Return Values
# Return single value
def square(x):
return x ** 2
# Return multiple values (as a tuple)
def min_max(numbers):
return min(numbers), max(numbers)
low, high = min_max([3, 1, 4, 1, 5, 9])
print(low, high) # 1 9
# Early return for guard clauses
def divide(a, b):
if b == 0:
return None # Early return โ don't proceed
return a / b
# Functions without return give None
def say_hi():
print("hi")
result = say_hi() # Prints "hi"
print(result) # NoneScope: Local vs Global
x = 10 # Global
def my_func():
y = 20 # Local โ only visible inside
print(x) # Can READ global x
print(y) # Can read local y
my_func()
print(x) # 10 โ accessible
# print(y) # NameError โ y doesn't exist outside the function
# To MODIFY a global inside a function
count = 0
def increment():
global count # Declare intent to modify global
count += 1 # Without 'global', this is UnboundLocalError
# nonlocal โ modify variable in enclosing function scope
def outer():
total = 0
def inner():
nonlocal total
total += 1
inner()
return totalDocstrings
def calculate_bmi(weight_kg: float, height_m: float) -> float:
"""
Calculate Body Mass Index (BMI).
Args:
weight_kg: Weight in kilograms.
height_m: Height in meters.
Returns:
BMI value rounded to 1 decimal place.
Example:
>>> calculate_bmi(70, 1.75)
22.9
"""
return round(weight_kg / height_m ** 2, 1)
# View docstring
help(calculate_bmi)
print(calculate_bmi.__doc__)Key Takeaways
- Write docstrings: document parameters, return values, and examples
- Never use mutable defaults: use
Noneand create inside function - *args is a tuple, **kwargs is a dict
- Return multiple values as tuples: unpack with
a, b = func() - Avoid global state: pass values as parameters instead
Practice Exercises
- Write
celsius_to_fahrenheit(c)andfahrenheit_to_celsius(f). Test both. - Write a function
word_count(text)that returns a dict of word frequencies. - Write a function that accepts any number of numbers and returns (min, max, average, sum).
- Write a function with proper docstring and type hints. Run
help(your_function).