ch10s1_DecoratorsAndMetaclasses
Python is not just a language; it’s a *framework for creating frameworks*.
Chapter 10: Advanced Topics — Decorators and Metaclasses
🧠 Decorators and Metaclasses: Extending Python’s Core Behavior
Python is not just a language; it’s a framework for creating frameworks.
Two of its most powerful meta-programming tools are decorators and metaclasses, which allow you to modify behavior at runtime — without touching the core logic of your code.
🎁 1. Understanding Decorators — Functions That Modify Functions
A decorator is a higher-order function that takes another function and returns a modified version of it.
You can think of it as a wrapper that adds extra functionality before or after the original function executes.
Basic Decorator Example
def uppercase_decorator(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result.upper()
return wrapper
@uppercase_decorator
def greet(name):
return f"Hello, {name}!"
print(greet("Alice")) # Output: HELLO, ALICE!
💡
@decorator_nameis shorthand forgreet = decorator_name(greet)
⚙️ 2. The Anatomy of a Decorator
Decorators rely on first-class functions (functions as objects) and closures (nested functions that remember their enclosing scope).
This allows you to dynamically add features like logging, security, or performance tracking.
Example — Logging Decorator
from functools import wraps
def log_function_call(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling function: {func.__name__} with args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@log_function_call
def add(a, b):
return a + b
add(3, 5)
Output:
Calling function: add with args=(3, 5), kwargs={}
add returned 8
⚠️ Always use
functools.wraps— it preserves function metadata like__name__and__doc__.
⏱️ 3. Practical Decorators in Real Projects
3.1 Timing Function Execution
import time
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} executed in {end - start:.4f}s")
return result
return wrapper
@timer
def slow_task():
time.sleep(1)
return "Done!"
slow_task()
3.2 Access Control Example
def require_admin(func):
@wraps(func)
def wrapper(user_role, *args, **kwargs):
if user_role != "admin":
raise PermissionError("Access denied: admin required.")
return func(user_role, *args, **kwargs)
return wrapper
@require_admin
def delete_database(user_role):
print("Database deleted!")
delete_database("admin")
# delete_database("guest") → raises PermissionError
🧩 4. Class and Method Decorators
Decorators can also modify methods or entire classes.
Example — Class Method Decorator
def validate_positive(func):
@wraps(func)
def wrapper(self, value):
if value < 0:
raise ValueError("Value must be positive.")
return func(self, value)
return wrapper
class Account:
def __init__(self, balance=0):
self.balance = balance
@validate_positive
def deposit(self, amount):
self.balance += amount
print(f"New balance: {self.balance}")
🧱 5. Understanding Metaclasses — The Class of Classes
If decorators modify functions, metaclasses modify classes.
A metaclass defines how a class behaves — just as a class defines how its instances behave.
When you create a class:
class Foo:
pass
Python internally does this:
Foo = type("Foo", (), {})
So, type is itself a metaclass!
🧬 6. Custom Metaclass Example
Singleton Pattern with Metaclass
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class DatabaseConnection(metaclass=SingletonMeta):
def __init__(self, name):
self.name = name
a = DatabaseConnection("main")
b = DatabaseConnection("replica")
print(a is b) # True → both refer to same instance
💡 The
__call__method intercepts instance creation, letting you customize class instantiation behavior.
🧩 7. Advanced Metaclass — Auto-Registering Classes
You can use metaclasses to automatically register or modify classes at creation time.
class AutoRegister(type):
registry = []
def __new__(cls, name, bases, attrs):
new_cls = super().__new__(cls, name, bases, attrs)
cls.registry.append(name)
return new_cls
class Base(metaclass=AutoRegister):
pass
class User(Base): pass
class Admin(Base): pass
print(AutoRegister.registry)
# Output: ['Base', 'User', 'Admin']
🧰 8. Combining Decorators and Metaclasses
They can be combined to create powerful frameworks.
Example: auto-logging every method call in all classes of a metaclass.
def log_methods(cls):
for attr, value in cls.__dict__.items():
if callable(value):
setattr(cls, attr, log_function_call(value))
return cls
@log_methods
class Calculator:
def add(self, a, b): return a + b
def multiply(self, a, b): return a * b
calc = Calculator()
calc.add(2, 3)
🧭 9. Decorators vs Metaclasses — When to Use Which
| Feature | Decorator | Metaclass |
|---|---|---|
| Target | Functions or Classes | Classes themselves |
| When Applied | At function definition time | At class creation time |
| Common Use Cases | Logging, timing, validation, caching | Singleton, class registration, schema enforcement |
| Complexity | Simple | Advanced |
| Scope | Local (per function/class) | Global (affects all class creation) |
🧠 Rule of Thumb: Use decorators for behavior changes. Use metaclasses when you need to modify how classes are built.
🧪 10. Real-World Example — ORM-Style Validation
class FieldEnforcer(type):
def __new__(mcls, name, bases, attrs):
if "fields" in attrs:
for field, ftype in attrs["fields"].items():
if not isinstance(ftype, type):
raise TypeError(f"Invalid field type for {field}")
return super().__new__(mcls, name, bases, attrs)
class Model(metaclass=FieldEnforcer):
pass
class User(Model):
fields = {"id": int, "name": str}
# Works fine
class InvalidModel(Model):
fields = {"age": "integer"} # Raises TypeError
💡 11. Best Practices
✅ Always use functools.wraps in decorators.
✅ Keep decorators small and modular — avoid “magic.”
✅ Use metaclasses sparingly — prefer composition and mixins first.
✅ Document all implicit behavior.
✅ Combine both tools for frameworks (e.g., Django ORM, Flask routing).
🧠 Summary
| Concept | Description | Example |
|---|---|---|
| Decorator | Function that wraps and modifies another function/class | @timer, @log |
| Metaclass | Class that defines behavior of other classes | class MyClass(metaclass=Meta) |
| Use Case | Modify runtime behavior | Customize class creation |
| Example Libraries | Flask, Django, FastAPI | SQLAlchemy, Pydantic |
Decorators enhance behavior, metaclasses shape structure. Mastering both gives you true control over Python’s dynamic nature.