Decorators in Python
Decorators are a powerful feature in Python that allow you to modify the behavior of functions or methods without changing their source code. They are applied using the @decorator_name
syntax above a function definition.
Basic Decorator
Here’s a simple decorator that prints a message before and after a function executes:
def my_decorator(func):
def wrapper():
print("Before function call")
func()
print("After function call")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
# Output:
# Before function call
# Hello!
# After function call
Note we use the wrapper
function to wrap the original function func
.
Decorators with Arguments
To create decorators that work with functions that take arguments, the wrapper function should accept *args
and **kwargs
:
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Before function call")
result = func(*args, **kwargs)
print("After function call")
return result
return wrapper
@my_decorator
def add(a, b):
return a + b
print(add(3, 5))
# Output:
# Before function call
# After function call
# 8
Decorators with Parameters
You can also create decorators that accept their own parameters:
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(n):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
# Output:
# Hello, Alice!
# Hello, Alice!
# Hello, Alice!
Practical Examples
Decorators are commonly used for:
Timing Functions
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"Function {func.__name__} took {time.time() - start:.2f} seconds to run")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
slow_function()
# Output: Function slow_function took 1.00 seconds to run
Authentication
def require_auth(func):
def wrapper(user, *args, **kwargs):
if not user.is_authenticated:
raise Exception("Authentication required")
return func(user, *args, **kwargs)
return wrapper
@require_auth
def view_profile(user):
return f"Welcome {user.name}!"
Caching Results
def cache(func):
stored_results = {}
def wrapper(*args):
if args in stored_results:
return stored_results[args]
result = func(*args)
stored_results[args] = result
return result
return wrapper
@cache
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(35)) # Fast even for large values
Built-in Decorators
Python has several built-in decorators:
# @property turns a method into a property
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def area(self):
return 3.14 * self._radius ** 2
# @classmethod creates a method that receives the class as the first argument
class MyClass:
@classmethod
def from_string(cls, string):
return cls(string.strip())
# @staticmethod creates a method that doesn't receive the instance or class
class MathUtils:
@staticmethod
def add(a, b):
return a + b
Next example: Enums