python修饰器(装饰器)以及wraps

装饰器的引入

初期及问题的诞生

假如现在在一个公司,有A B C三个业务部门,还有S一个基础服务部门,目前呢,S部门提供了两个函数,供其他部门调用,函数如下:

在初期,其他部门这样调用是没有问题的,随着公司业务的发展,现在S部门需要对函数调用假如权限验证,如果有权限的话,才能进行调用,否则调用失败。考虑一下,如果是我们,该怎么做呢?

方案集合
1、让调用方也就是ABC部门在调用的时候,先主动进行权限验证
2、S部门在对外提供的函数中,首先进行权限认证,然后再进行真正的函数操作
问题
在第一种方案中,不应该暴露给外层的授权认证应该在使用之前暴露出来。同时,如果有多个部门,每个部门的每个人都应该知道你肯定别人会这样做,这是不可靠的。

[En]

In the first option, the authority authentication that should not be exposed to the outer layer should be exposed before the use. At the same time, if there are multiple departments, everyone in each department should know that you are sure that others will do this, which is unreliable.

方案二,看似看行,可是当S部门对外提供更多的需要进行权限验证方法时,每个函数都要调用权限验证,同样也实在费劲,不利于代码的维护性和扩展性

那么,有没有办法通过遵循代码开闭的原则来完美地解决这个问题呢?

[En]

So, is there a way to solve this problem perfectly by following the principle of code opening and closing?

装饰器引入
答案肯定是肯定的,否则就真的很弱。首先看一下代码。

[En]

The answer must be yes, otherwise it would be really weak. Look at the code first.

输出结果为

可以通过代码及输出看到,在调用f1 f2 函数时,成功进行了权限验证,那么是怎么做到的呢?其实这里就使用到了装饰器,通过定义一个闭包函数w1,在我们调用函数上通过关键词@w1,这样就对f1 f2函数完成了装饰。

装饰器原理
首先,开看我们的装饰器函数w1,该函数接收一个参数func,其实就是接收一个方法名,w1内部又定义一个函数inner,在inner函数中增加权限校验,并在验证完权限后调用传进来的参数func,同时w1的返回值为内部函数inner,其实就是一个闭包函数。

然后,再来看一下,在f1上增加@w1,那这是什么意思呢?当python解释器执行到这句话的时候,会去调用w1函数,同时将被装饰的函数名作为参数传入(此时为f1),根据闭包一文分析,在执行w1函数的时候,此时直接把inner函数返回了,同时把它赋值给f1,此时的f1已经不是未加装饰时的f1了,而是指向了w1.inner函数地址。相当于f1=w1(f1)

接下来,在调用f1()的时候,其实调用的是w1.inner函数,那么此时就会先执行权限验证,然后再调用原来的f1(),该处的f1就是通过装饰传进来的参数f1。

这样下来,就完成了对f1的装饰,实现了权限验证。

装饰器知识点

在了解了装饰师的原理后,它的执行时机是什么?让我们来看看它。

[En]

After understanding the principle of the decorator, what is the timing of its execution? let’s take a look at it.

国际惯例,代码优先

[En]

International practice, code first

输出结果为

由此可以发现,当python解释器执行到@w1时,就开始进行装饰了,相当于执行了如下代码:

当有两个或更多的装饰者装饰一个函数时,执行过程和装饰结果是什么?同样,代码说明了这个问题。

[En]

When there are two or more decorators decorating a function, what is the execution process and decoration result? Again, the problem is illustrated by code.

输出结果:

可以发现,先用第二个装饰器(makeItalic)进行装饰,接着再用第一个装饰器(makeBold)进行装饰,而在调用过程中,先执行第一个装饰器(makeBold),接着再执行第二个装饰器(makeItalic)。

为什么呢,分两步来分析一下。

1、装饰时机 通过上面装饰时机的介绍,我们可以知道,在执行到@makeBold的时候,需要对下面的函数进行装饰,此时解释器继续往下走,发现并不是一个函数名,而又是一个装饰器,这时候,@makeBold装饰器暂停执行,而接着执行接下来的装饰器@makeItalic,接着把test函数名传入装饰器函数,从而打印’b’,在makeItalic装饰完后,此时的test指向makeItalic的inner函数地址,这时候有返回来执行@makeBold,接着把新test传入makeBold装饰器函数中,因此打印了’a’。
2、在调用test函数的时候,根据上述分析,此时test指向makeBold.inner函数,因此会先打印’1’,接下来,在调用fun()的时候,其实是调用的makeItalic.inner()函数,所以打印’2’,在makeItalic.inner中,调用的fun其实才是我们最原声的test函数,所以打印原test函数中的’c’,’3’,所以在一层层调完之后,打印的结果为hello python decorator

上面例子中的f1 f2都是对无参函数的装饰,不再单独举例

在使用中,一些函数可能会带参数,那么如何处理呢?

[En]

In use, some functions may take arguments, so how to deal with this?

代码优先:

输出结果为:

指定代码注释已经可用,因此不再单独解释它们。

[En]

Specify that code comments are already available, so they will no longer be explained separately.

在这一点上,你可能会问,这是一个参数,如果有多个或不确定长度的参数,怎么办?看一看下面的代码,您就会明白。

[En]

At this point, you may ask, that is a parameter, if there are multiple or indefinite length parameters, what to do? Take a look at the following code and you’ll see.

输出结果为:

利用python的可变参数轻松实现装饰带参数的函数。

对带有返回值的函数进行装饰

让我们用返回值来修饰函数。根据前面的编写,代码如下所示。

[En]

Let’s decorate the function with the return value. According to the previous writing, the code looks like this.

输出结果为:

可以发现,此时,并没有输出test函数的’hello’,而是None,那是为什么呢,可以发现,在inner函数中对test进行了调用,但是没有接受不了返回值,也没有进行返回,那么默认就是None了,知道了原因,那么来修改一下代码:

输出结果:

这实现了预期,并完成了使用返回参数装饰函数。

[En]

This achieves the expectation and finishes decorating the function with return parameters.

带参数的装饰器

本文介绍了带参数的函数和带返回值的函数的修饰,那么有没有带参数的修饰符,如果有,有什么用?

[En]

This paper introduces the decoration of functions with parameters and functions with return values, so is there a decorator with parameters, and if so, what is the use?

答案肯定是肯定的,让我们通过代码来看看。

[En]

The answer must be yes, let’s take a look at it through the code.

输出结果为:

简单理解,带参数的装饰器就是在原闭包的基础上又加了一层闭包,通过外层函数func_args的返回值w_test_log就看出来了,具体执行流程在注释里已经说明了。
其优点是可以在运行时根据不同的参数处理不同的应用程序功能。

[En]

The advantage is that different application functions can be processed according to different parameters at run time.

通用装饰器
介绍了这么多,在实际应用中,如果没有一类函数来写装饰器,估计是用尽了,那么就没有万能的装饰器了,答案一定是肯定的,不要再胡说八道,直接上代码。

[En]

Introduced so much, in practical application, if there is no category of functions to write a decorator, it is estimated to be exhausted, then there is no universal universal decorator, the answer must be yes, no more nonsense, directly on the code.

输出结果为:

结合上面的例子来完成万能装饰器的功能,原理和上面一样,没有太多的废话。

[En]

Combine the above examples to complete the function of the universal decorator, the principle is the same as above, it is not too much nonsense.

类装饰器

当创建一个对象后,直接去执行这个对象,那么是会抛出异常的,因为他不是callable,无法直接执行,但进行修改后,就可以直接执行调用了,如下

输出为:

接下来,让我们进入正题,看看如何用类来装饰函数。

[En]

Next, let’s get to the point and take a look at how to decorate functions with classes.

输出结果为:

和之前的原理一样,当python解释器执行到到@Test时,会把当前test函数作为参数传入Test对象,调用init方法,同时将test函数指向创建的Test对象,那么在接下来执行test()的时候,其实就是直接对创建的对象进行调用,执行其call方法。

在了解 wraps修饰器之前,我们首先要了解 partialupdate_wrapper这两个函数,因为在 wraps的代码中,用到了这两个函数。

partial

首先说 partial函数,在官方文档的描述中,这个函数的声明如下: functools.partial(func, *args, **keywords)。它的作用就是返回一个 partial对象,当这个 partial对象被调用的时候,就像通过 func(*args, **kwargs)的形式来调用 func函数一样。如果有额外的 位置参数(args) 或者 关键字参数(*kwargs) 被传给了这个 partial对象,那它们也都会被传递给 func函数,如果一个参数被多次传入,那么后面的值会覆盖前面的值。

个人感觉这个函数很像C++中的 bind函数,都是把某个函数的某个参数固定,从而构造出一个新的函数来。比如下面这个例子:

这个函数是使用C而不是Python实现的,但是官方文档中给出了Python实现的代码,如下所示,大家可以进行参考:

update_wrapper

接下来,我们再来聊一聊 update_wrapper这个函数,顾名思义,这个函数就是用来更新修饰器函数的,具体更新些什么呢,我们可以直接把它的源码搬过来看一下:

大家可以发现,这个函数的作用就是从 被修饰的函数(wrapped) 中取出一些属性值来,赋值给 修饰器函数(wrapper) 。为什么要这么做呢,我们看下面这个例子。

首先,我们来编写一个自定义修饰符,它没有任何功能,只有一个文档字符串,如下所示:

[En]

First of all, let’s write a custom modifier, which has no function, only a document string, as follows:

从上面的例子我们可以看到,我想要获取 wrapped这个被修饰函数的文档字符串,但是却获取成了 wrapper_function的文档字符串, wrapped函数的名字也变成了 wrapper_function函数的名字。这是因为给 wrapped添加上 @wrapper修饰器相当于执行了一句 wrapped = wrapper(wrapped),执行完这条语句之后, wrapped函数就变成了 wrapper_function函数。遇到这种情况该怎么办呢,首先我们可以手动地在 wrapper函数中更改 wrapper_function__doc____name__属性,但聪明的你肯定也想到了,我们可以直接用 update_wrapper函数来实现这个功能。

我们对上面定义的修饰器稍作修改,添加了一句 update_wrapper(wrapper_function, f)

此时我们可以发现, __doc____name__属性已经能够按我们预想的那样显示了,除此之外, update_wrapper函数也对 __module____dict__等属性进行了更改和更新。

OK,至此,我们已经了解了 partialupdate_wrapper这两个函数的功能,接下来我们翻出 wraps修饰器的源码:

没错,就是这么的简单,只有这么一句,我们可以看出, wraps函数其实就是一个修饰器版的 update_wrapper函数,它的功能和 update_wrapper是一模一样的。我们可以修改我们上面的自定义修饰器的例子,做出一个更方便阅读的版本。

自定义修饰器v3

至此,我想大家应该明白 wraps这个修饰器的作用了吧,就是将 被修饰的函数(wrapped) 的一些属性值赋值给 修饰器函数(wrapper) ,最终让属性的显示更符合我们的直觉。

https://segmentfault.com/a/1190000009398663

https://blog.csdn.net/u010358168/article/details/77773199

Original: https://www.cnblogs.com/slysky/p/9777424.html
Author: 勇敢的公爵
Title: python修饰器(装饰器)以及wraps

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/7822/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

最近整理资源【免费获取】:   👉 程序员最新必读书单  | 👏 互联网各方向面试题下载 | ✌️计算机核心资源汇总