Intermediate

Functions

Learn to define reusable functions, handle parameters flexibly, use lambda expressions, understand scope, and get started with decorators.

Defining Functions

Functions are reusable blocks of code defined with the def keyword:

Python
def greet(name):
    """Greet a person by name."""
    print(f"Hello, {name}!")

greet("Alice")   # Hello, Alice!
greet("Bob")     # Hello, Bob!

Parameters and Arguments

Python
# Positional arguments
def add(a, b):
    return a + b

print(add(3, 5))    # 8

# Keyword arguments
def describe(name, age, city):
    print(f"{name} is {age} from {city}")

describe(age=30, city="NYC", name="Alice")

Return Values

Python
def calculate(a, b):
    """Return multiple values as a tuple."""
    total = a + b
    diff = a - b
    product = a * b
    return total, diff, product

s, d, p = calculate(10, 3)
print(s, d, p)  # 13 7 30

Default Parameters

Python
def power(base, exponent=2):
    return base ** exponent

print(power(5))      # 25 (uses default exponent=2)
print(power(5, 3))   # 125
Never use mutable default arguments! Using def func(items=[]) is a common bug — the list is shared across all calls. Use def func(items=None) and set items = items or [] inside the function.

*args and **kwargs

Python
# *args - variable positional arguments (tuple)
def sum_all(*args):
    return sum(args)

print(sum_all(1, 2, 3, 4))  # 10

# **kwargs - variable keyword arguments (dict)
def build_profile(**kwargs):
    return kwargs

profile = build_profile(name="Alice", age=30, role="Engineer")
print(profile)  # {'name': 'Alice', 'age': 30, 'role': 'Engineer'}

# Combining all parameter types
def example(required, *args, default=10, **kwargs):
    print(required, args, default, kwargs)

Lambda Functions

Anonymous, single-expression functions:

Python
# Lambda syntax
square = lambda x: x ** 2
print(square(5))  # 25

# Common use: sorting with custom key
students = [("Alice", 90), ("Bob", 85), ("Charlie", 92)]
students.sort(key=lambda s: s[1], reverse=True)
# [('Charlie', 92), ('Alice', 90), ('Bob', 85)]

Scope: Local vs Global

Python
x = 10  # Global variable

def outer():
    y = 20  # Local to outer

    def inner():
        z = 30  # Local to inner
        print(x, y, z)  # Can access all three

    inner()

outer()  # 10 20 30

# Modify global inside function
def increment():
    global x
    x += 1

increment()
print(x)  # 11

Docstrings

Python
def calculate_bmi(weight_kg, height_m):
    """
    Calculate Body Mass Index (BMI).

    Args:
        weight_kg (float): Weight in kilograms.
        height_m (float): Height in meters.

    Returns:
        float: The BMI value.

    Example:
        >>> calculate_bmi(70, 1.75)
        22.86
    """
    return round(weight_kg / height_m ** 2, 2)

Built-in Functions

Python
nums = [1, 2, 3, 4, 5]

# map - apply function to each element
doubled = list(map(lambda x: x * 2, nums))
# [2, 4, 6, 8, 10]

# filter - keep elements that match condition
evens = list(filter(lambda x: x % 2 == 0, nums))
# [2, 4]

# zip - pair elements from multiple iterables
names = ["Alice", "Bob"]
scores = [95, 87]
for name, score in zip(names, scores):
    print(f"{name}: {score}")

# enumerate - loop with index
for i, val in enumerate(nums, start=1):
    print(f"{i}. {val}")

Decorators (Introduction)

Decorators wrap a function to extend its behavior without modifying it:

Python
import time

def timer(func):
    """Decorator that measures execution time."""
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - start
        print(f"{func.__name__} took {elapsed:.4f}s")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(1)
    return "Done!"

slow_function()  # slow_function took 1.0012s
Functions are first-class objects in Python. You can assign them to variables, pass them as arguments, and return them from other functions. This is what makes decorators and higher-order functions possible.