Functions are reusable blocks of code. Python functions are first-class objects — they can be passed as arguments, returned from other functions, and stored in variables.
Defining Functions
Python
# Basic function
def greet(name):
"""Greet a user by name.""" # Docstring (helps with documentation)
return f"Hello, {name}!"
# Function with multiple parameters
def calculate_area(length, width):
"""Calculate the area of a rectangle."""
return length * width
# Default parameters
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
greet("Alice") # "Hello, Alice!"
greet("Bob", "Hi") # "Hi, Bob!"
# Keyword arguments (can be in any order)
def create_user(name, age, email):
return {"name": name, "age": age, "email": email}
user = create_user(age=28, email="alice@test.com", name="Alice")*args and **kwargs
Python
# *args — variable number of positional arguments
def sum_all(*args):
"""Sum any number of arguments."""
return sum(args)
print(sum_all(1, 2, 3)) # 6
print(sum_all(1, 2, 3, 4, 5)) # 15
# **kwargs — variable number of keyword arguments
def print_info(**kwargs):
"""Print any keyword arguments."""
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Alice", age=28, city="NYC")
# Combining both
def func(required, *args, **kwargs):
print(f"Required: {required}")
print(f"Args: {args}")
print(f"Kwargs: {kwargs}")
func("hello", 1, 2, 3, key="value")
# Required: hello
# Args: (1, 2, 3)
# Kwargs: {'key': 'value'}Lambda Functions
Python
# Lambda — anonymous, one-line functions
square = lambda x: x ** 2
add = lambda a, b: a + b
print(square(5)) # 25
print(add(3, 4)) # 7
# Lambdas are most useful with higher-order functions
numbers = [5, 2, 8, 1, 9, 3]
# sorted with key function
sorted_numbers = sorted(numbers, key=lambda x: -x) # [9, 8, 5, 3, 2, 1]
# With lists of tuples
students = [("Alice", 95), ("Bob", 87), ("Charlie", 92)]
by_grade = sorted(students, key=lambda s: s[1], reverse=True)
# [('Alice', 95), ('Charlie', 92), ('Bob', 87)]
# map — apply function to every element
doubled = list(map(lambda x: x * 2, [1, 2, 3])) # [2, 4, 6]
# filter — keep elements that pass the test
evens = list(filter(lambda x: x % 2 == 0, range(10))) # [0, 2, 4, 6, 8]Higher-Order Functions
Python
# Functions that take or return other functions
def apply_twice(func, value):
"""Apply a function twice to a value."""
return func(func(value))
print(apply_twice(lambda x: x + 3, 7)) # 13 (7+3=10, 10+3=13)
# Function composition
def compose(f, g):
"""Compose two functions: f(g(x))."""
return lambda x: f(g(x))
double = lambda x: x * 2
increment = lambda x: x + 1
double_then_increment = compose(increment, double)
print(double_then_increment(5)) # 11 (5*2=10, 10+1=11)Closures
Python
# A closure captures variables from its enclosing scope
def make_multiplier(factor):
"""Return a function that multiplies by factor."""
def multiplier(x):
return x * factor
return multiplier
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
# Practical: a simple counter
def make_counter(start=0):
count = [start] # List to allow mutation in closure
def counter():
count[0] += 1
return count[0]
return counter
c = make_counter()
print(c()) # 1
print(c()) # 2
print(c()) # 3Decorators
Python
import time
# A decorator wraps a function with extra behavior
def timer(func):
"""Measure execution time."""
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end-start:.4f}s")
return result
return wrapper
@timer # Same as: slow_function = timer(slow_function)
def slow_function():
time.sleep(1)
return "Done!"
slow_function() # "slow_function took 1.0012s"
# Practical: retry decorator
def retry(max_attempts=3):
def decorator(func):
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
raise
print(f"Attempt {attempt+1} failed: {e}")
return wrapper
return decorator
@retry(max_attempts=3)
def unreliable_function():
import random
if random.random() < 0.7:
raise ValueError("Random failure!")
return "Success!"Knowledge Check
4 questions — test your understanding
1
What is the output of: list(map(lambda x: x*2, [1,2,3]))?
2
What does *args represent in a function definition?
3
What is a closure in Python?
4
What does @timer do above a function?