You think a function is fixed once defined. It is not.
In Python, functions are objects—and can be modified at runtime.
Today, you understand how decorators actually work under the hood.
Today’s Goal
By the end of today, you will:
- Understand how decorators work internally
- Learn function wrapping
- Understand closures
- Use decorators for real-world patterns
The Illusion
def greet():
print("hello")
You think:
greet is just a function
Reality:
greet is an object that can be passed, modified, and replaced
Functions Are First-Class Objects
def greet():
print("hello")
f = greet
f()
Functions can be:
- assigned
- passed
- returned
What Is a Decorator?
A decorator is:
a function that takes another function and returns a new function
Basic Example
def decorator(func):
def wrapper():
print("before")
func()
print("after")
return wrapper
Applying Decorator
@decorator
def greet():
print("hello")
Equivalent to:
greet = decorator(greet)
Key Insight
Decorators replace your function with another function.
Closures (Critical)
def outer():
x = 10
def inner():
print(x)
return inner
Inner function remembers x.
Why Closures Matter
Decorators rely on closures to:
- retain original function
- maintain state
Decorator with Arguments
def decorator(func):
def wrapper(*args, **kwargs):
print("before")
result = func(*args, **kwargs)
print("after")
return result
return wrapper
Metadata Problem
print(greet.__name__)
Returns:
wrapper
Fix with functools.wraps
from functools import wraps
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
Parameterized Decorator
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return wrapper
return decorator
Example Usage
@repeat(3)
def greet():
print("hi")
Real-World Use Cases
- logging
- authentication
- caching
- timing functions
Example — Timing Decorator
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print("time:", end - start)
return result
return wrapper
Stacking Decorators
@d1
@d2
def f():
pass
Equivalent:
f = d1(d2(f))
Why This Matters
Decorators allow:
- clean abstraction
- separation of concerns
- reusable behavior
Your Task
- build logging decorator
- implement retry decorator
- stack multiple decorators
Common Mistakes
- forgetting *args, **kwargs
- losing function metadata
- overusing decorators
Think Deeper
- how does closure store variables?
- how does decorator replace function?
- when should you avoid decorators?
Subtle Insight (CRITICAL)
Decorators modify behavior without changing original code.
Tomorrow
Concurrency & Async — how Python handles parallel work
Rule
- decorators wrap, they don’t change original logic
- use for cross-cutting concerns
See you in Day 10.