Python Decorators Explained: Understanding Advanced Python Features
Dive into Python decorators and learn how they can help you write cleaner, more maintainable code.

Python Decorators Explained: Understanding Advanced Python Features
Decorators are one of Python's most powerful features, allowing you to modify the behavior of functions or methods without changing their source code. This concept follows Python's principle of "open for extension, closed for modification."
Understanding Python Decorators
At its core, a decorator is a function that takes another function as an argument and extends its behavior without explicitly modifying it.
PYTHON1def my_decorator(func): 2 def wrapper(): 3 print("Something is happening before the function is called.") 4 func() 5 print("Something is happening after the function is called.") 6 return wrapper 7 8@my_decorator 9def say_hello(): 10 print("Hello!") 11 12# When you call say_hello() 13say_hello()
The output of this code would be:
Something is happening before the function is called.
Hello!
Something is happening after the function is called.
How Decorators Work
The @my_decorator
syntax is just syntactic sugar for:
PYTHON1def say_hello(): 2 print("Hello!") 3 4say_hello = my_decorator(say_hello)
This is why decorators are so powerful - they allow you to modify or extend functions without changing their source code.
Decorators with Arguments
Sometimes you need to pass arguments to the decorated function. Here's how to handle that:
PYTHON1def my_decorator(func): 2 def wrapper(*args, **kwargs): 3 print("Before the function call") 4 result = func(*args, **kwargs) 5 print("After the function call") 6 return result 7 return wrapper 8 9@my_decorator 10def greet(name): 11 print(f"Hello, {name}!") 12 13greet("Python Developer")
Decorators with Parameters
You can also create decorators that accept parameters:
PYTHON1def repeat(n): 2 def decorator(func): 3 def wrapper(*args, **kwargs): 4 for _ in range(n): 5 result = func(*args, **kwargs) 6 return result 7 return wrapper 8 return decorator 9 10@repeat(3) 11def say_hi(name): 12 print(f"Hi, {name}!") 13 14say_hi("Python Expert") # Will print "Hi, Python Expert!" three times
Practical Examples of Decorators
Timing Functions
PYTHON1import time 2 3def timing_decorator(func): 4 def wrapper(*args, **kwargs): 5 start_time = time.time() 6 result = func(*args, **kwargs) 7 end_time = time.time() 8 print(f"{func.__name__} took {end_time - start_time:.6f} seconds to run") 9 return result 10 return wrapper 11 12@timing_decorator 13def slow_function(): 14 time.sleep(1) 15 print("Function executed") 16 17slow_function()
Caching Results (Memoization)
PYTHON1def memoize(func): 2 cache = {} 3 4 def wrapper(*args): 5 if args in cache: 6 return cache[args] 7 result = func(*args) 8 cache[args] = result 9 return result 10 return wrapper 11 12@memoize 13def fibonacci(n): 14 if n <= 1: 15 return n 16 return fibonacci(n-1) + fibonacci(n-2) 17 18print(fibonacci(35)) # Without memoization, this would be very slow
Authentication
PYTHON1def require_auth(func): 2 def wrapper(user, *args, **kwargs): 3 if not user.is_authenticated: 4 raise PermissionError("Authentication required") 5 return func(user, *args, **kwargs) 6 return wrapper 7 8@require_auth 9def view_protected_resource(user, resource_id): 10 return f"User {user.name} is viewing resource {resource_id}"
Class Decorators
Decorators can also be applied to classes:
PYTHON1def add_greeting(cls): 2 cls.greet = lambda self: f"Hello, I'm {self.name}" 3 return cls 4 5@add_greeting 6class Person: 7 def __init__(self, name): 8 self.name = name 9 10person = Person("John") 11print(person.greet()) # Output: Hello, I'm John
Method Decorators
Python has built-in decorators like @classmethod
, @staticmethod
, and @property
:
PYTHON1class Temperature: 2 def __init__(self, celsius=0): 3 self._celsius = celsius 4 5 @property 6 def celsius(self): 7 return self._celsius 8 9 @celsius.setter 10 def celsius(self, value): 11 if value < -273.15: 12 raise ValueError("Temperature below absolute zero is not possible") 13 self._celsius = value 14 15 @property 16 def fahrenheit(self): 17 return self._celsius * 9/5 + 32 18 19 @fahrenheit.setter 20 def fahrenheit(self, value): 21 self.celsius = (value - 32) * 5/9 22 23 @classmethod 24 def from_fahrenheit(cls, value): 25 return cls((value - 32) * 5/9)
Stacking Decorators
You can apply multiple decorators to a single function:
PYTHON1@decorator1 2@decorator2 3def my_function(): 4 pass
This is equivalent to:
PYTHON1my_function = decorator1(decorator2(my_function))
The decorators are applied from bottom to top (decorator2 first, then decorator1).
Best Practices
- Use
functools.wraps
: This preserves the metadata of the original function
PYTHON1from functools import wraps 2 3def my_decorator(func): 4 @wraps(func) 5 def wrapper(*args, **kwargs): 6 return func(*args, **kwargs) 7 return wrapper
- Keep decorators simple: Each decorator should have a single responsibility
- Document what your decorators do: They can make code harder to understand if not well documented
Conclusion
Decorators are a powerful Python feature that enables clean, maintainable code by separating concerns and following the principle of "open for extension, closed for modification."
Mastering decorators will help you write more elegant, reusable, and maintainable Python code. They're widely used in frameworks like Flask, Django, and many other Python libraries to provide clean APIs and separate cross-cutting concerns from business logic.