Location>code7788 >text

13 Python Object-Oriented Programming: Decorators

Popularity:758 ℃/2024-09-05 10:02:38

This is the 13th in a series of Python tutorials; for more, visit my Python collection!

Python decorators are a powerful tool for modifying or enhancing the behavior of a function or method without changing its source code. A decorator is essentially a function that takes a function as an argument and returns a new function. Uses for decorators include logging, performance testing, transaction processing, caching, permission checking, and more!

1 Basic syntax

The basic syntax of a decorator is to precede a function definition with a@symbol that immediately follows the name of the decorator. Example:

# Define a decorator with arguments to the method being decorated
def my_decorator(func).
    def wrapper().
        print("before the method runs")
        func()
        print("after the method is run")

    return wrapper

# Use the decorator with "@".
@my_decorator
def say_hello().
    print("Hello!")

say_hello()

This code will output:

Before the method runs
Hello!
method is run

2 Parameter Passing

If the decorated function takes arguments, the decorator needs to handle them accordingly:

def my_decorator(func):
    def wrapper(name):
        print("Before the method runs")
        func(name)
        print("After the method runs")

    return wrapper

@my_decorator
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")

Output:

Before the method runs
Hello, Alice!
After the method runs

Parameters can be more flexible with variable parameters, as follows:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Before the method runs")
        func(*args, **kwargs)
        print("After the method runs")

    return wrapper

@my_decorator
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")

3 Using multiple decorators

You can use multiple decorators for the same function:

def decorator_A(func):
    print("enter A")
    def wrapper():
        print("Before A")
        func()
        print("After A")
    print("out A")
    return wrapper
 
def decorator_B(func):
    print("enter B")
    def wrapper():
        print("Before B")
        func()
        print("After B")
    print("out B")
    return wrapper
 
@decorator_A
@decorator_B
def my_function():
    print("Inside my_function")
 
# Execute the function decorated by the decorator
my_function()

Output:

enter B
out B
enter A
out A
Before A
Before B
Inside my_function
After B
After A

Note the order in which the results are printed.

For ease of expression we'll start by calling decorators that are close to the method being modified inner decorators, such as in the example@decorator_BThe ones that aren't close are called outer decorators, as in the example@decorator_A

in closureswrapperThe code outside is the inner decorator executed first, in the closurewrapperThe internal code execution order is a bit more complex:① The outer decorator is executed firstfunc() Previous code-> ② inner decorator executionfunc() Previous code ->③Executionfunc() ->④ Inner decorator executionfunc() Code behind ->⑤ outer decorator executionfunc() Code behind.

4 Passing parameters to the decorator

The decorator itself can accept parameters, and the behavior of the decorator can be changed depending on the different parameters passed in.

The previous examples are all decorators without parameters, so what if we want to pass parameters to the decorator? So we think about what can receive parameters, the answer is a function. bingo! Python is also designed in this way, we just need to wrap a layer of function outside the decorator, you can pass the parameters to the function and then to the decorator.

A decorator can be defined like this:

def repeat(num_times):
    def decorator_repeat(func):
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator_repeat

@repeat(3)
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")

Output:

Hello, Alice!
Hello, Alice!
Hello, Alice!

This defines a decorator that executes a function the specified number of times based on the value passed into the decorator.

5 Classes as Decorators

5.1 __call__methodologies

Decorators can be not only methods but also classes. This brings us to a special method__call__

Python classes that implement the__call__ With this special method, an instance object of the class can be called like a function, because when trying to write the object as a method call (name + ()), the Python interpreter looks for the object's__call__ method and call it.

Here's a simple example that demonstrates__call__The use of:

class Counter:
    def __init__(self):
         = 0

    def __call__(self, *args, **kwargs):
         += 1
        print(f"method is called {} substandard")

counter = Counter()

# analog call
counter()
counter()
counter()

Print:

Method called 1 time
Method called 2 times
Method called 3 times

5.2 Classes as Decorators

One of the main advantages of classes as decorators is that state can be easily maintained, since classes can have instance variables.

get it__call__After that, we can think of classes as decorators on the principle that the class implements the__call__method that makes the decorator's code executable.

Below we define a decorator that records the number of function calls:

class CallCounter:
    def __init__(self, func):
         = func
         = 0

    def __call__(self, *args, **kwargs):
         += 1
        print(f"{.__name__} was called {} substandard")
        return (*args, **kwargs)

@CallCounter
def say_hello(name):
    print(f"Hello, {name}!")

# Calling a decorated function
say_hello("Alice")
say_hello("Bob")
say_hello("Charlie")

# exports
# say_hello was called 1 substandard
# Hello, Alice!
# say_hello was called 2 substandard
# Hello, Bob!
# say_hello was called 3 substandard
# Hello, Charlie!

Code Explanation:

  1. CallCounter Classes have a constructor__init__, it must accept a function as an argument.
  2. class implements the__call__ method, which allows its instances to be called like functions.
  3. exist__call__ method, the counter is incremented each time the decorated function is calledcount value and prints the number of times the function has been called.
  4. Finally.__call__ method calls the original function and return the result.