Decorators extend the functionality of functions or methods in Python. They allow you to "wrap" another function, modifying its behavior. In this tutorial, we'll cover the basics of decorators, provide examples of how to use built-in decorators, and demonstrate how to create your own custom decorators. ## Basics of Decorators A decorator is a function that takes another function and extends its behavior without explicitly modifying it. This is useful for cross-cutting concerns like logging, access control, and instrumentation. ### Simple Decorator Example Let's start with a simple example to illustrate the concept.
def my_decorator(func): def wrapper(): print("Something is happening before the function is called.") func() print("Something is happening after the function is called.") return wrapper def say_hello(): print("Hello!") # Apply the decorator decorated_say_hello = my_decorator(say_hello) decorated_say_hello()
Something is happening before the function is called. Hello! Something is happening after the function is called.
**Explanation:** - `my_decorator`: The decorator function that takes a function `func` as an argument. - `wrapper`: The nested function that adds additional behavior before and after calling `func`. - `decorated_say_hello`: The function `say_hello` wrapped by `my_decorator`, extending its behavior. ### Using Python's `@` Syntax Instead of wrapping functions manually, Python provides the `@` syntax to apply decorators, making the code cleaner and more readable.
def my_decorator(func): def wrapper(): print("Something is happening before the function is called.") func() print("Something is happening after the function is called.") return wrapper @my_decorator def say_hello(): print("Hello!") say_hello()
Something is happening before the function is called. Hello! Something is happening after the function is called.
In this example, the `@my_decorator` syntax is syntactic sugar for `say_hello = my_decorator(say_hello)`. ## Built-in Decorators Python provides some built-in decorators like `@staticmethod`, `@classmethod`, and `@property`. Let's look at a few examples. ### `@staticmethod` and `@classmethod` These decorators are commonly used in classes.
class MyClass: @staticmethod def static_method(): print("This is a static method.") @classmethod def class_method(cls): print(f"This is a class method of {cls}.") # Usage MyClass.static_method() MyClass.class_method()
This is a static method. This is a class method of <class '__main__.MyClass'>.
**Explanation:** - `@staticmethod`: Declares a method that does not operate on an instance or class variable. - `@classmethod`: Declares a method that receives the class itself as the first argument, commonly named `cls`. ### `@property` The `@property` decorator is used to define getters and setters for class attributes.
class Circle: def __init__(self, radius): self._radius = radius @property def radius(self): return self._radius @radius.setter def radius(self, value): if value < 0: raise ValueError("Radius cannot be negative.") self._radius = value # Usage c = Circle(5) print(c.radius) c.radius = 10 print(c.radius)
5 10
**Explanation:** - `@property`: Decorates the getter method. - `@<property>.setter`: Decorates the setter method. ## Creating Custom Decorators You can create your own decorators to encapsulate repetitive tasks or enhance functions with additional behavior. ### Custom Decorator with Arguments Decorators can also take arguments. Let's create a decorator that logs the execution time of a function.
import time def timer(func): def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) end_time = time.time() print(f"Function '{func.__name__}' took {end_time - start_time:.4f} seconds to execute.") return result return wrapper @timer def slow_function(seconds): time.sleep(seconds) return "Done sleeping!" # Usage print(slow_function(2))
Function 'slow_function' took 2.0027 seconds to execute. Done sleeping!
**Explanation:** - `wrapper(*args, **kwargs)`: Ensures the decorator can handle functions with parameters. - `start_time` and `end_time`: Measure the execution time of the function. - `@timer`: Applies the `timer` decorator to `slow_function`. ### Chaining Decorators You can also chain multiple decorators on the same function.
def decorator1(func): def wrapper(): print("Decorator 1") func() return wrapper def decorator2(func): def wrapper(): print("Decorator 2") func() return wrapper @decorator1 @decorator2 def say_hello(): print("Hello!") say_hello()
Decorator 1 Decorator 2 Hello!
**Explanation:** - `@decorator1` and `@decorator2`: Both decorators are applied to `say_hello`. - The order of application is bottom to top, so `decorator2` is applied first, followed by `decorator1`.