Skip to main content

Module 13 - Decorators & Closures

This module covers decorators and closures in Python. You'll learn how to modify function behavior, create reusable wrappers, and understand closure scope.


1. Understanding Closures

1.1 What is a Closure?

A closure is a function that remembers values from its enclosing scope, even when the function is executed outside that scope.

def outer(x):
def inner(y):
return x + y # inner "closes over" x
return inner

add_5 = outer(5)
print(add_5(3)) # 8
print(add_5(10)) # 15

1.2 Practical Closure Example

def multiplier(factor):
def multiply(number):
return number * factor
return multiply

double = multiplier(2)
triple = multiplier(3)

print(double(5)) # 10
print(triple(5)) # 15

2. Function Decorators

2.1 Basic Decorator

def uppercase_decorator(func):
def wrapper():
result = func()
return result.upper()
return wrapper

@uppercase_decorator
def greet():
return "hello, world"

print(greet()) # HELLO, WORLD

# Equivalent to:
# greet = uppercase_decorator(greet)

2.2 Decorator with Arguments

def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator

@repeat(3)
def say_hello(name):
print(f"Hello, {name}!")

say_hello("Alice")
# Hello, Alice!
# Hello, Alice!
# Hello, Alice!

3. Common Decorator Patterns

3.1 Timing Decorator

import time
from functools import wraps

def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end-start:.4f} seconds")
return result
return wrapper

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

slow_function() # slow_function took 1.0001 seconds

3.2 Caching Decorator

from functools import wraps

def memoize(func):
cache = {}
@wraps(func)
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)

print(fibonacci(100)) # Fast due to caching

4. Class Decorators

def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance

@singleton
class Database:
def __init__(self):
print("Database initialized")

db1 = Database() # Database initialized
db2 = Database() # No print (same instance)
print(db1 is db2) # True

5. Built-in Decorators

5.1 @property, @classmethod, @staticmethod

class Circle:
def __init__(self, radius):
self._radius = radius

@property
def radius(self):
return self._radius

@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("Radius cannot be negative")
self._radius = value

@property
def area(self):
return 3.14159 * self._radius ** 2

@classmethod
def from_diameter(cls, diameter):
return cls(diameter / 2)

@staticmethod
def is_valid_radius(radius):
return radius > 0

circle = Circle(5)
print(circle.area) # 78.53975

circle2 = Circle.from_diameter(10)
print(circle2.radius) # 5.0

6. Summary

ConceptDescriptionExample
ClosureFunction remembering outer scopedef outer(): def inner(): ...
DecoratorFunction modifying another function@decorator
@wrapsPreserve function metadatafrom functools import wraps
Stacked decoratorsMultiple decorators on one function@dec1 @dec2 def func():
Key Takeaways
  • Decorators modify function behavior without changing code
  • Closures remember outer scope variables
  • Use @wraps to preserve function metadata
  • Built-in decorators: @property, @classmethod, @staticmethod

7. What's Next?

In Module 14 - Iterators & Generators, you'll learn:

  • Creating iterators
  • Generator functions
  • Generator expressions
  • yield keyword

8. Practice Exercises

Exercise 1: Logger Decorator

Create a decorator that logs function calls with arguments.

Exercise 2: Validation Decorator

Build a decorator that validates function arguments.

Exercise 3: Retry Decorator

Implement a decorator that retries failed function calls.

Exercise 4: Rate Limiter

Create a decorator that limits function call frequency.

Exercise 5: Authentication Decorator

Build a decorator for access control.

Solutions

Try solving these exercises on your own first. Solutions will be provided in the practice section.