Python Tutorials

Choose a lesson to begin

Functions, Decorators, and Functional Programming

Functions are the building blocks of reusable code. Mastering functions, decorators, and functional programming patterns will make you a significantly better Python developer.

Function Fundamentals

Basic Function Definition

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

Call the function

message = greet("Alice") print(message) # Hello, Alice!

Anatomy:

Multiple Parameters

def add(a, b):
    return a + b
def describe_person(name, age, city):
    return f"{name} is {age} years old and lives in {city}."
result = add(5, 3)  # 8
info = describe_person("Alice", 30, "NYC")

Default Parameters

def power(base, exponent=2):
    return base ** exponent
print(power(5))      # 25 (uses default exponent=2)
print(power(5, 3))   # 125 (overrides default)
def create_user(username, role="user", active=True):
    return {
        "username": username,
        "role": role,
        "active": active
    }
user1 = create_user("alice")
# {'username': 'alice', 'role': 'user', 'active': True}
user2 = create_user("admin", role="admin")
# {'username': 'admin', 'role': 'admin', 'active': True}

Keyword Arguments

def configure(host, port, timeout, retry):
    print(f"{host}:{port}, timeout={timeout}, retry={retry}")

Positional (must match order)

configure("localhost", 8080, 30, True)

Named (any order)

configure(port=8080, host="localhost", retry=True, timeout=30)

Mix (positional first, then keyword)

configure("localhost", 8080, timeout=30, retry=True)

Variable Arguments

*args - Variable Positional Arguments

def sum_all(*numbers):
    """Sum any number of arguments."""
    total = 0
    for num in numbers:
        total += num
    return total
print(sum_all(1, 2, 3))           # 6
print(sum_all(10, 20, 30, 40))    # 100

Example: flexible printing

def print_items(*items, sep=" | "): print(sep.join(str(item) for item in items)) print_items("A", "B", "C") # A | B | C print_items(1, 2, 3, sep=", ") # 1, 2, 3

**kwargs - Variable Keyword Arguments

def create_profile(**details):
    """Create profile from any keyword arguments."""
    for key, value in details.items():
        print(f"{key}: {value}")
create_profile(name="Alice", age=30, city="NYC")
# name: Alice
# age: 30
# city: NYC

Combine everything

def flexible_function(required, *args, default=10, **kwargs): print(f"Required: {required}") print(f"Extra positional: {args}") print(f"Default: {default}") print(f"Extra keyword: {kwargs}") flexible_function(1, 2, 3, default=20, x=100, y=200)

Required: 1

Extra positional: (2, 3)

Default: 20

Extra keyword: {'x': 100, 'y': 200}

Return Values

Multiple Returns

def get_stats(numbers):
    return min(numbers), max(numbers), sum(numbers) / len(numbers)

Unpack returned values

minimum, maximum, average = get_stats([10, 20, 30, 40]) print(f"Min: {minimum}, Max: {maximum}, Avg: {average}")

Or keep as tuple

stats = get_stats([5, 10, 15]) print(stats) # (5, 15, 10.0)

Early Returns

def validate_age(age):
    if age < 0:
        return "Invalid age"
    if age < 18:
        return "Minor"
    if age < 65:
        return "Adult"
    return "Senior"
print(validate_age(25))  # Adult
print(validate_age(-5))  # Invalid age

Return None

def greet(name):
    print(f"Hello, {name}!")
    # No return statement = returns None
result = greet("Alice")  # Prints: Hello, Alice!
print(result)            # None

Lambda Functions

Anonymous functions for simple operations:

# Regular function
def double(x):
    return x * 2

Lambda equivalent

double = lambda x: x * 2 print(double(5)) # 10

With multiple parameters

add = lambda a, b: a + b print(add(3, 4)) # 7

Typically used inline

numbers = [1, 2, 3, 4, 5] doubled = list(map(lambda x: x * 2, numbers)) print(doubled) # [2, 4, 6, 8, 10]

When to use lambdas:

When NOT to use lambdas:

Functional Programming Patterns

map() - Transform Elements

numbers = [1, 2, 3, 4, 5]

Using map

squared = list(map(lambda x: x**2, numbers)) print(squared) # [1, 4, 9, 16, 25]

Equivalent list comprehension (preferred in Python)

squared = [x**2 for x in numbers]

Map with named function

def celsius_to_fahrenheit(c): return (c * 9/5) + 32 temps_c = [0, 10, 20, 30] temps_f = list(map(celsius_to_fahrenheit, temps_c)) print(temps_f) # [32.0, 50.0, 68.0, 86.0]

filter() - Select Elements

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Using filter

evens = list(filter(lambda x: x % 2 == 0, numbers)) print(evens) # [2, 4, 6, 8, 10]

Equivalent list comprehension (preferred)

evens = [x for x in numbers if x % 2 == 0]

Filter with named function

def is_adult(age): return age >= 18 ages = [12, 25, 17, 30, 16, 22] adults = list(filter(is_adult, ages)) print(adults) # [25, 30, 22]

reduce() - Accumulate Values

from functools import reduce
numbers = [1, 2, 3, 4, 5]

Sum all numbers

total = reduce(lambda acc, x: acc + x, numbers) print(total) # 15

Find maximum

maximum = reduce(lambda a, b: a if a > b else b, numbers) print(maximum) # 5

More readable: use built-in functions

total = sum(numbers) maximum = max(numbers)

Decorators

Decorators modify or enhance functions:

Basic Decorator

def uppercase_decorator(func):
    """Decorator that converts result to uppercase."""
    def wrapper():
        result = func()
        return result.upper()
    return wrapper
@uppercase_decorator
def greet():
    return "hello, world!"
print(greet())  # HELLO, WORLD!

Decorator with Arguments

def timer(func):
    """Measure function execution time."""
    import 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
def slow_function(n):
    total = 0
    for i in range(n):
        total += i
    return total
result = slow_function(1000000)
# slow_function took 0.0523s

Multiple Decorators

def bold(func):
    def wrapper():
        return f"<b>{func()}</b>"
    return wrapper
def italic(func):
    def wrapper():
        return f"<i>{func()}</i>"
    return wrapper
@bold
@italic
def greet():
    return "Hello"
print(greet())  # <b><i>Hello</i></b>

Practical Decorator Examples

# Cache results
def memoize(func):
    cache = {}
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrapper
@memoize
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

Validate inputs

def validate_positive(func): def wrapper(x): if x <= 0: raise ValueError("Must be positive") return func(x) return wrapper @validate_positive def square_root(x): return x ** 0.5

Log function calls

def log_calls(func): def wrapper(*args, **kwargs): print(f"Calling {func.__name__} with {args} {kwargs}") result = func(*args, **kwargs) print(f"{func.__name__} returned {result}") return result return wrapper @log_calls def add(a, b): return a + b

First-Class Functions

Functions are objects - can be passed around:

def add(a, b):
    return a + b
def subtract(a, b):
    return a - b
def apply_operation(func, x, y):
    """Apply any function to two numbers."""
    return func(x, y)
result1 = apply_operation(add, 10, 5)       # 15
result2 = apply_operation(subtract, 10, 5)  # 5

Store functions in data structures

operations = { "add": add, "subtract": subtract, "multiply": lambda a, b: a * b } op = operations["add"] print(op(3, 4)) # 7

Closures

Functions that remember values from enclosing scope:

def make_multiplier(factor):
    """Create a function that multiplies by factor."""
    def multiply(x):
        return x * factor  # 'factor' is remembered
    return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5))  # 10
print(triple(5))  # 15

Practical example: create validators

def make_validator(min_val, max_val): def validate(value): return min_val <= value <= max_val return validate is_valid_age = make_validator(0, 120) is_valid_percentage = make_validator(0, 100) print(is_valid_age(25)) # True print(is_valid_age(150)) # False print(is_valid_percentage(50)) # True

Recursion

Functions calling themselves:

def factorial(n):
    """Calculate n! recursively."""
    if n <= 1:
        return 1
    return n * factorial(n - 1)
print(factorial(5))  # 120

Countdown example

def countdown(n): if n <= 0: print("Blast off!") else: print(n) countdown(n - 1) countdown(5) # 5 # 4 # 3 # 2 # 1

Blast off!

Directory tree traversal

import os def list_files(path, indent=0): for item in os.listdir(path): print(" " * indent + item) item_path = os.path.join(path, item) if os.path.isdir(item_path): list_files(item_path, indent + 1)

Try It Out!

  • Write a function that calculates BMI with default height unit
  • Create a decorator that counts function calls
  • Use map/filter to process a list of dictionaries
  • Write a closure that creates custom greeting functions
  • Implement a recursive function to sum digits of a number
  • Key Takeaways

    Functions are the foundation of clean, maintainable Python code! 🐍

    🐍 Python Runner