Six ways to write Python decorators

Preface

The text and pictures in this article are from the Internet and are for learning and communication purposes only. They do not have any commercial use. If you have any questions, please contact us for processing.

PS: If you need Python learning materials, you can click on the link below to get it yourself

Python free learning materials and group communication answers Click to join


Today I will share with you the knowledge points about decorators. The content is very dry and full of energy. After carefully absorbing and reading, you will definitely have a deeper understanding of decorators.

Hello, decorator

The decorator is essentially a Python function, which allows other functions to add additional functions without any code changes. The return value of the decorator is also a function object.

It is often used in scenarios with aspect requirements, such as inserting logs, performance testing, transaction processing, caching, and permission verification.

Decorator is an excellent design to solve this kind of problem. With decorator, we can extract a lot of similar code that has nothing to do with the function itself and continue to reuse it.

The use of decorators is very fixed

  • First define a decorator (hat)
  • Then define your business function or class (person)
  • Finally put the decorator (hat) on the head of this function (person)

Like below

# 定义装饰器
def decorator(func):
    def wrapper(*args, **kw):
        return func()
    return wrapper

# 定义业务函数并进行装饰
@decorator
def function():
    print("hello, decorator")

In fact, decorators are not necessary for coding, which means that you can use decorators without them. Its appearance should make our code

  • More elegant and clearer code structure
  • Encapsulate specific function codes into decorators to improve code reuse rate and enhance code readability

Next, I will explain with examples how to write a variety of simple and complex decorators.

The first type: ordinary decorator

First of all, let's write a most common decorator, its function is:

  • Before the function is executed, record a line of log
  • After the function is executed, record a line of log
# 这是装饰器函数,参数 func 是被装饰的函数
def logger(func):
    def wrapper(*args, **kw):
        print('我准备开始执行:{} 函数了:'.format(func.__name__))

        # 真正执行的是这行。
        func(*args, **kw)

        print('主人,我执行完啦。')
    return wrapper

Suppose, my business function is to calculate the sum of two numbers. After writing it, put a hat on it.

@logger
def add(x, y):
    print('{} + {} = {}'.format(x, y, x+y))

Then execute the add function.

add(200, 50)

Let's see what is output?

我准备开始执行:add 函数了:
200 + 50 = 250
我执行完啦。

The second type: function decorator with parameters

Through the above two simple introductory examples, you should be able to appreciate the working principle of the decorator.

However, the use of decorators is much more than that, and there are still many articles to study. Let's learn this knowledge point together today.

Looking back at the above example, the decorator cannot receive parameters. Its usage can only be applied to some simple scenarios. A decorator that does not pass parameters can only perform fixed logic on the decorated function.

The decorator itself is a function. As a function, if you can't pass parameters, then the function of this function will be very limited and can only execute fixed logic. This means that if the execution of the logic code of the decorator needs to be adjusted according to different scenarios, if the parameters cannot be passed, we have to write two decorators, which is obviously unreasonable.

For example, if we want to implement a task that can send emails regularly (send one per minute) and time synchronization tasks (synchronize once a day), we can implement a periodic_task (timed task) decorator by ourselves. This decorator can receive A time interval parameter, how often the task will be executed once.

You can write it like the following. Since this function code is more complicated and not conducive to learning, it will not be posted here.

@periodic_task(spacing=60)
def send_mail():
     pass

@periodic_task(spacing=86400)
def ntp()
    pass 

Then let's create a pseudo-scene ourselves. We can pass in a parameter in the decorator to indicate the nationality, and before the function is executed, we can say hello in our native language.

# 小明,中国人
@say_hello("china")
def xiaoming():
    pass

# jack,美国人
@say_hello("america")
def jack():
    pass

What if we implement this decorator so that it can pass parameters?

It will be more complicated and requires two levels of nesting.

def say_hello(contry):
    def wrapper(func):
        def deco(*args, **kwargs):
            if contry == "china":
                print("你好!")
            elif contry == "america":
                print('hello.')
            else:
                return

            # 真正执行函数的地方
            func(*args, **kwargs)
        return deco
    return wrapper

Let's execute

xiaoming()
print("------------")
jack()

Look at the output.

你好!
------------
hello.

The third type: class decorator without parameters

The above are all decorators based on functions. When reading other people's code, you can often find decorators based on classes.

Based on the implementation of the class decorator, two built-in functions call  and  init must be implemented  .
__ init __  : receive the decorated function
__ call __  : implement the decoration logic.

Take the simple example of log printing as an example

class logger(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("[INFO]: the function {func}() is running..."\
            .format(func=self.func.__name__))
        return self.func(*args, **kwargs)

@logger
def say(something):
    print("say {}!".format(something))

say("hello")

Run it and see the output

[INFO]: the function say() is running...
say hello!

The fourth type: class decorator with parameters

In the above example without parameters, you find that there is none. Only INFO-level logs can be printed. Under normal circumstances, we also need to print DEBUG WARNING logs. This requires passing parameters to the class decorator and assigning the level to this function.

There is a big difference between class decorators with and without parameters.

__init__  : No longer receives the decorated function, but instead receives the incoming parameters.
__ call __  : Receive the decorated function and implement the decoration logic.

class logger(object):
    def __init__(self, level='INFO'):
        self.level = level

    def __call__(self, func): # 接受函数
        def wrapper(*args, **kwargs):
            print("[{level}]: the function {func}() is running..."\
                .format(level=self.level, func=func.__name__))
            func(*args, **kwargs)
        return wrapper  #返回函数

@logger(level='WARNING')
def say(something):
    print("say {}!".format(something))

say("hello")

We specify the WARNING level, run it, and see the output.

[WARNING]: the function say() is running...
say hello!

Fifth: Use partial functions and classes to implement decorators

Most decorators are implemented based on functions and closures, but this is not the only way to make decorators.

In fact, Python has only one requirement for whether an object can be used in the form of a decorator (@decorator): the decorator must be a "callable" object.

For this callable object, we are most familiar with the function.

In addition to functions, a class can also be a callable object, as long as it implements the __ call __  function (I have already touched the above few examples).

Partial functions that are easily overlooked are actually callable objects.

Next, let's talk about how to use a combination of classes and partial functions to implement a distinctive decorator.

As shown below, DelayFunc is a  class that implements  __ call __ . Delay returns a partial function, where delay can be used as a decorator. (The following code is taken from Python Craftsman: Tips for Using Decorators)

import time
import functools

class DelayFunc:
    def __init__(self,  duration, func):
        self.duration = duration
        self.func = func

    def __call__(self, *args, **kwargs):
        print(f'Wait for {self.duration} seconds...')
        time.sleep(self.duration)
        return self.func(*args, **kwargs)

    def eager_call(self, *args, **kwargs):
        print('Call without delay')
        return self.func(*args, **kwargs)

def delay(duration):
    """
    装饰器:推迟某个函数的执行。
    同时提供 .eager_call 方法立即执行
    """
    # 此处为了避免定义额外函数,
    # 直接使用 functools.partial 帮助构造 DelayFunc 实例
    return functools.partial(DelayFunc, duration)

Our business function is very simple, just add

@delay(duration=2)
def add(a, b):
    return a+b

Take a look at the execution process

>>> add    # 可见 add 变成了 Delay 的实例
<__main__.DelayFunc object at 0x107bd0be0>
>>> 
>>> add(3,5)  # 直接调用实例,进入 __call__
Wait for 2 seconds...
8
>>> 
>>> add.func # 实现实例方法
<function add at 0x107bef1e0>

The sixth type: decorators that can decorate

When writing singleton mode in Python, there are three common ways of writing. One of them is implemented with decorators.

The following is the singleton version of the decorator version I wrote myself.

instances = {}

def singleton(cls):
    def get_instance(*args, **kw):
        cls_name = cls.__name__
        print('===== 1 ====')
        if not cls_name in instances:
            print('===== 2 ====')
            instance = cls(*args, **kw)
            instances[cls_name] = instance
        return instances[cls_name]
    return get_instance

@singleton
class User:
    _instance = None

    def __init__(self, name):
        print('===== 3 ====')
        self.name = name

You can see that we use the singleton decoration function to decorate the User class. Decorators are not very common in classes, but as long as you are familiar with the implementation process of decorators, it is not difficult to decorate classes. In the above example, the decorator just controls the generation of class instances.

The process of its instantiation, you can refer to my debugging process here to understand.

 

Guess you like

Origin blog.csdn.net/pythonxuexi123/article/details/112886953