What Are Decorators In Python?
Decorators in Python are a way to modify or extend the behavior of a function or class without changing its source code. They are implemented as a special kind of function that takes a function or class as input and returns a modified version of that function or class, usually by wrapping it in a higher-level function or class. Decorators can be used to add or remove functionality, control access to a function or class, or to cache results.
Decorators are typically used in the following cases:
- Adding Metadata to Functions or Classes- Decorators can be used to add extra information or metadata to functions or classes, such as setting a user-defined tag or documentation.
- Implementing Access Control- Decorators can be used to control access to functions or classes, such as setting authentication, authorization, or access restrictions.
- Implementing Caching- Decorators can be used to cache the results of expensive function calls, so that subsequent calls to the same function with the same arguments can be avoided.
- Modifying Function Behavior- Decorators can be used to modify the behavior of a function, such as adding a timer or a counter, or to enforce coding standards, such as input validation.
- Implementing Decorator Patterns- Decorators can be used to implement commonly used design patterns, such as singleton, factory, or observer patterns.
These are just a few examples, and the use of decorators is not limited to these cases. It is up to the developer to determine the appropriate use of decorators in their project.
History Of Decorators
Decorators in Python were introduced in version 2.4 and were popularized by Guido van Rossum, the creator of Python. The concept of decorators was inspired by the aspect-oriented programming paradigm, which aims to provide a clean and modular way to add additional behavior to code.
In Python, decorators were introduced as a way to modify the behavior of functions or classes in a more concise and readable manner. Prior to the introduction of decorators, this kind of modification was often done using longer and more complex code, making the overall design harder to understand and maintain.
The use of decorators in Python has become a popular way to add functionality to functions and classes, such as logging, timing, memoization, and access control, among others. They allow developers to write reusable and composable code that can be easily extended or modified without having to modify the code of the decorated functions or classes.
Overall, decorators are considered an important feature of the Python language, and have become a standard part of many development patterns and practices.
Some Example Of Decorators
Timing Decorator - A decorator that measures the time taken by a function to execute and prints it.
import time
def timing_decorator(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start:.2f} seconds")
return result
return wrapper
@timing_decorator
def long_running_function():
time.sleep(2)
long_running_function()
Logging Decorator- A decorator that logs the arguments and returns the value of a function.
def logging_decorator(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with arguments {args} and keyword arguments {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@logging_decorator
def add(a, b):
return a + b
add(1, 2)
Caching Decorator- A decorator that caches the results of a function so that subsequent calls with the same arguments return the cached result.
def caching_decorator(func):
cache = {}
def wrapper(*args, **kwargs):
key = (args, tuple(sorted(kwargs.items())))
if key in cache:
return cache[key]
result = func(*args, **kwargs)
cache[key] = result
return result
return wrapper
@caching_decorator
def expensive_function(a, b, c=0):
return a + b + c
expensive_function(1, 2, 3)
expensive_function(1, 2, 3)
Authorization Decorator- A decorator that checks if the user is authorized to access a function and raises an exception if not.
def authorization_decorator(func):
def wrapper(*args, **kwargs):
user = kwargs.get("user", None)
if user is None or not user.is_authenticated:
raise Exception("Unauthorized")
return func(*args, **kwargs)
return wrapper
@authorization_decorator
def protected_function(user):
return "Welcome, {}".format(user.username)
protected_function(user=User(username="johndoe", is_authenticated=True))
Argument Validation Decorator- A decorator that checks if the arguments passed to a function meet certain conditions and raises an exception if not.
def validation_decorator(func):
def wrapper(*args, **kwargs):
for arg in args:
if not isinstance(arg, int):
raise TypeError("All arguments must be integers")
return func(*args, **kwargs)
return wrapper
@validation_decorator
def sum_function(*args):
return sum(args)
sum_function(1, 2, 3)
Singleton Decorator- A decorator that ensures that a class only has one instance and returns that instance whenever the class is instantiated.
def singleton_decorator(cls):
instances = {}
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton_decorator
class SingletonClass:
pass
a = SingletonClass()
b = SingletonClass()
assert a is b
TLDR;
In conclusion, decorators are a powerful feature in Python that allow developers to modify the behavior of functions and classes in a clean and modular way. By using decorators, developers can add functionality such as logging, timing, memoization, and access control to their code, without having to modify the code of the decorated functions or classes.
Overall, decorators are an important tool in the Python developer's toolkit and are widely used in many development patterns and practices. If you're new to decorators, I recommend exploring some of the examples discussed in this chat to get a feel for how they work and how they can be used to simplify your code.