An article decorators get to know all the uses (recommended collection)

01. decorator syntax sugar #

If you touch Python for some time, then surely you @sign certainly no stranger, yes @symbol is syntactic sugar decorator.

Place it in a function defined at the beginning, it's like a hat worn on the head of this function. And the function to bind together. When we call this function, the first thing is not to perform this function, but this function as a parameter passed it the hat on his head, the hat we call 装饰函数or 装饰器.

You want to ask me what the decorator function can be achieved? I can only say how big the hole in your brain, there are more powerful decorator.

Use decorator is fixed:

  • Define a decorative function (hat) (class may also be used, to achieve partial function)
  • And then define your business function or class (people)
  • Finally this hat with the man in the head

Simple usage decorator there are many here cite two common.

  • Log the printer
  • Timer

02. Getting Started Usage: log printer unit #

First log the printer .

Realization of functions:

  • Before the function execution, the first print line of the log to inform about the owner, I want to perform a function.
  • In the function executed, we can not quit the jobs, and we but polite code, and then print the next line of the log to inform the owner, I am done for execution.
# 这是装饰函数
def logger(func):
    def wrapper(*args, **kw):
        print('我准备开始计算:{} 函数了:'.format(func.__name__))

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

        print('啊哈,我计算完啦。给自己加个鸡腿!!')
    return wrapper

If my business function is to calculate the sum of the two numbers. After written, it directly to a hat.

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

Then we calculated it.

add(200, 50)

Come see what the output, not magic?

我准备开始计算:add 函数了:
200 + 50 = 250
啊哈,我计算完啦。给自己加个鸡腿!

03. Getting Started Usage: timer #

Look at the timer
Function: As the name suggests, is a long time to perform a calculation function.

# 这是装饰函数
def timer(func):
    def wrapper(*args, **kw):
        t1=time.time()
        # 这是函数真正执行的地方
        func(*args, **kw)
        t2=time.time()

        # 计算下时长
        cost_time = t2-t1 
        print("花费时间:{}秒".format(cost_time))
    return wrapper

If, our function is to sleep 10 seconds. Long in the end do not fly when this can be better seen in this calculation.

import time

@timer
def want_sleep(sleep_time):
    time.sleep(sleep_time)

want_sleep(10)

Take a look at the output. Jesus really is 10 seconds. Really calendar harm! ! !

花费时间:10.0073800086975098

04. Advanced Usage: decorator function with parameters #

By the above simple entry, you probably have felt the magic of the decoration.

However, the use of decorators is far more than that. Today, it is imperative that we thoroughly publicize knowledge.

The above example is not decorated reception parameters. Its use can only apply some simple scene. Not decorators parameter passing, a function can only be decorated, fixed logic execution.

If you have experience, you must frequently in the project, saw some decoration is with parameters.

Decorator itself is a function, since it can not function as a carrying function, that function this function is very limited. Only perform fixed logic. This is very unreasonable. And if we have to use two broadly consistent with the content, just different in some parts of logic. The Senate does not pass, we must write two decorators. Xiao Ming think this can not tolerate.

So how decorators to achieve 传参it, it would be more complex, requiring two layers of nesting.

Similarly, we also give you an example.

We have to be in the implementation of these two functions, respectively, according to their nationality, for a period of greeting words.

def american():
    print("我来自中国。")

def chinese():
    print("I am from America.")

When the two of them put on the decorator, the decorator will now say, this one is which country, then the decorator will make judgments, hit the corresponding call.

After wearing the hat, it is this.

@say_hello("china")
def american():
    print("我来自中国。")

@say_hello("america")
def chinese():
    print("I am from America.")

Everything is ready, only a thin hat. To define what needed here two nested.

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

About the implementation

american()
print("------------")
chinese()

Take a look at the output.

你好!
我来自中国。
------------
hello.
I am from America

emmmm, it is very NB. . .

05. higher-order Usage: decorator class with no arguments #

These are based decorator function implemented when reading others code can also be found that there is often based on class implements decorators.

Decorator based implementation class must implement __call__and __init__two built-in functions.
__init__: Receiving decorative function
__call__: decoration realized logic.

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")

Execute it and see output

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

06. higher-order Usage: decorator class with arguments #

The above example without parameters, you found that only print INFOlevel logging, under normal circumstances, we also need to print DEBUG WARNINGgrade else logs. This need to pass parameters to the class decorator, to specify the level of this function.

Decorator class with parameters and without parameters are very different.

__init__: No longer receive decorative function, but receives incoming parameters.
__call__: Receiving decorative function to achieve 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 specified WARNINGlevel, run about, take a look at the output.

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

07. Use of the class implement partial functions decorator #

The vast majority are based decorator, but this is not the only way to manufacture a decorator's functions and closures implemented.

In fact, Python as to whether an object can Decorator ( @decoratoruse) in the form of only one request: Decorator must be a "can be called (callable) objects .

For this callable objects, we are most familiar with is the function of.

In addition to functions, classes may be callable object, as long to achieve the __call__function (several boxes been exposed above a), and partial functions are less people use callable objects.

Then say it, how to use classes and partial function combined to achieve a distinctive decorator.

As shown below, DelayFunc is an implementation __call__of the class, delay function returns a partial, where delay can be used as a decorator. (The following is an excerpt Python craftsman: the decorator tips)

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 simply to sum

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

Look at the implementation process

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

08. How to write the class can decorate decorator? #

Written in Python singletons time, there are three common wording. One is achieved by decorators.

The following is a single case of writing my own writing decorators version.

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

We can see this with singleton decorative function to decorate the User class. Used for decoration on the class, not very common, but as long as familiar with the implementation of the decorator, it is not difficult to decorate for the class. In the above example, the decorator just achieve control of the generated class instance only.

Examples of the process, you can refer to the debugging process I have here, to be understood.

09. wraps decorator dim? #

在 functools 标准库中有提供一个 wraps 装饰器,你应该也经常见过,那他有啥用呢?

先来看一个例子

def wrapper(func):
    def inner_function():
        pass
    return inner_function

@wrapper
def wrapped():
    pass

print(wrapped.__name__)
#inner_function

为什么会这样子?不是应该返回 func 吗?

这也不难理解,因为上边执行func 和下边 decorator(func) 是等价的,所以上面 func.__name__ 是等价于下面decorator(func).__name__ 的,那当然名字是 inner_function

def wrapper(func):
    def inner_function():
        pass
    return inner_function

def wrapped():
    pass

print(wrapper(wrapped).__name__)
#inner_function

那如何避免这种情况的产生?方法是使用 functools .wraps 装饰器,它的作用就是将 被修饰的函数(wrapped) 的一些属性值赋值给 修饰器函数(wrapper) ,最终让属性的显示更符合我们的直觉。

from functools import update_wrapper

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                       '__annotations__')

def wrapper(func):
    def inner_function():
        pass

    update_wrapper(inner_function, func, assigned=WRAPPER_ASSIGNMENTS)
    return inner_function

@wrapper
def wrapped():
    pass

print(wrapped.__name__)

准确点说,wraps 其实是一个偏函数对象(partial),源码如下

def wraps(wrapped,
          assigned = WRAPPER_ASSIGNMENTS,
          updated = WRAPPER_UPDATES):
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)

可以看到wraps其实就是调用了一个函数update_wrapper,知道原理后,我们改写上面的代码,在不使用 wraps的情况下,也可以让 wrapped.__name__ 打印出 wrapped,代码如下:

from functools import update_wrapper

def wrapper(func):
    def inner_function():
        pass
    update_wrapper(func, inner_function)
    return inner_function

@wrapper
def wrapped():
    pass

print(wrapped.__name__)
# wrapped

10. 内置装饰器:property#

以上,我们介绍的都是自定义的装饰器。

其实Python语言本身也有一些装饰器。比如property这个内建装饰器,我们再熟悉不过了。

它通常存在于类中,可以将一个函数定义成一个属性,属性的值就是该函数return的内容。

通常我们给实例绑定属性是这样的

class Student(object):
    def __init__(self, name, age=None):
        self.name = name
        self.age = age

# 实例化
XiaoMing = Student("小明")

# 添加属性
XiaoMing.age=25

# 查询属性
XiaoMing.age

# 删除属性
del XiaoMing.age

但是稍有经验的开发人员,一下就可以看出,这样直接把属性暴露出去,虽然写起来很简单,但是并不能对属性的值做合法性限制。为了实现这个功能,我们可以这样写。

class Student(object):
    def __init__(self, name):
        self.name = name
        self.name = None

    def set_age(self, age):
        if not isinstance(age, int):
            raise ValueError('输入不合法:年龄必须为数值!')
        if not 0 < age < 100:
            raise ValueError('输入不合法:年龄范围必须0-100')
        self._age=age

    def get_age(self):
        return self._age

    def del_age(self):
        self._age = None


XiaoMing = Student("小明")

# 添加属性
XiaoMing.set_age(25)

# 查询属性
XiaoMing.get_age()

# 删除属性
XiaoMing.del_age()

上面的代码设计虽然可以变量的定义,但是可以发现不管是获取还是赋值(通过函数)都和我们平时见到的不一样。
按照我们思维习惯应该是这样的。

# 赋值
XiaoMing.age = 25

# 获取
XiaoMing.age

那么这样的方式我们如何实现呢。请看下面的代码。

class Student(object):
    def __init__(self, name):
        self.name = name
        self.name = None

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if not isinstance(value, int):
            raise ValueError('输入不合法:年龄必须为数值!')
        if not 0 < value < 100:
            raise ValueError('输入不合法:年龄范围必须0-100')
        self._age=value

    @age.deleter
    def age(self):
        del self._age

XiaoMing = Student("小明")

# 设置属性
XiaoMing.age = 25

# 查询属性
XiaoMing.age

# 删除属性
del XiaoMing.age

@property装饰过的函数,会将一个函数定义成一个属性,属性的值就是该函数return的内容。同时,会将这个函数变成另外一个装饰器。就像后面我们使用的@age.setter@age.deleter

@age.setter 使得我们可以使用XiaoMing.age = 25这样的方式直接赋值。
@age.deleter 使得我们可以使用del XiaoMing.age这样的方式来删除属性。

property 的底层实现机制是「描述符」,为此我还写过一篇文章。

这里也介绍一下吧,正好将这些看似零散的文章全部串起来。

如下,我写了一个类,里面使用了 property 将 math 变成了类实例的属性

class Student:
    def __init__(self, name):
        self.name = name

    @property
    def math(self):
        return self._math

    @math.setter
    def math(self, value):
        if 0 <= value <= 100:
            self._math = value
        else:
            raise ValueError("Valid value must be in [0, 100]")

为什么说 property 底层是基于描述符协议的呢?通过 PyCharm 点击进入 property 的源码,很可惜,只是一份类似文档一样的伪源码,并没有其具体的实现逻辑。

不过,从这份伪源码的魔法函数结构组成,可以大体知道其实现逻辑。

这里我自己通过模仿其函数结构,结合「描述符协议」来自己实现类 property 特性。

代码如下:

class TestProperty(object):

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        print("in __get__")
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError
        return self.fget(obj)

    def __set__(self, obj, value):
        print("in __set__")
        if self.fset is None:
            raise AttributeError
        self.fset(obj, value)

    def __delete__(self, obj):
        print("in __delete__")
        if self.fdel is None:
            raise AttributeError
        self.fdel(obj)


    def getter(self, fget):
        print("in getter")
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        print("in setter")
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        print("in deleter")
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

然后 Student 类,我们也相应改成如下

class Student:
    def __init__(self, name):
        self.name = name

    # 其实只有这里改变
    @TestProperty
    def math(self):
        return self._math

    @math.setter
    def math(self, value):
        if 0 <= value <= 100:
            self._math = value
        else:
            raise ValueError("Valid value must be in [0, 100]")

为了尽量让你少产生一点疑惑,我这里做两点说明:

  1. 使用TestProperty装饰后,math 不再是一个函数,而是TestProperty 类的一个实例。所以第二个math函数可以使用 math.setter 来装饰,本质是调用TestProperty.setter 来产生一个新的 TestProperty 实例赋值给第二个math
  2. 第一个 math 和第二个 math 是两个不同 TestProperty 实例。但他们都属于同一个描述符类(TestProperty),当对 math 对于赋值时,就会进入 TestProperty.__set__,当对math 进行取值里,就会进入 TestProperty.__get__。仔细一看,其实最终访问的还是Student实例的 _math 属性。

说了这么多,还是运行一下,更加直观一点。

# 运行后,会直接打印这一行,这是在实例化 TestProperty 并赋值给第二个math
in setter
>>>
>>> s1.math = 90
in __set__
>>> s1.math
in __get__
90

如对上面代码的运行原理,有疑问的同学,请务必结合上面两点说明加以理解,那两点相当关键。

11. 其他装饰器:装饰器实战#

读完并理解了上面的内容,你可以说是Python高手了。别怀疑,自信点,因为很多人都不知道装饰器有这么多用法呢。

在我看来,使用装饰器,可以达到如下目的:

  • 使代码可读性更高,逼格更高;
  • 代码结构更加清晰,代码冗余度更低;

刚好我在最近也有一个场景,可以用装饰器很好的实现,暂且放上来看看。

这是一个实现控制函数运行超时的装饰器。如果超时,则会抛出超时异常。

有兴趣的可以看看。

Guess you like

Origin www.cnblogs.com/gaodi2345/p/11671420.html