装饰器

Posted sweltering

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了装饰器相关的知识,希望对你有一定的参考价值。

装饰器

一、装饰器的引入

有一个需求,一个加法函数,想要增强它的功能,能够输出被调用过以及调用的参数信息,这种需求应该如何解决呢?

1 # 加法函数 add 的定义
2 def add(x, y):
3     return x+y
4 # 增强它的功能,输出被调用过以及调用的参数信息
5 def add(x, y):
6     print("call add, x = {}, y = {}".format(x, y))
7     return x+y

思考:功能是增强了,但是这种直接在原来只计算值的函数中增加属于非业务功能的代码,这种方式好吗?肯定是不好的,缺点如下:

  1. 首先打印语句的耦合性太高了

  2. 加法函数属于业务的逻辑,输出信息属于非业务功能的代码,不应该直接放在该函数中。

代码改进,重新定义一个函数,在这个函数中来做输出信息的功能:

 1 def add(x, y):
 2     return x+y
 3 ?
 4 def message_show(fn):
 5     print("begin...call add")
 6     result = fn(4, 5)
 7     print("end...")
 8     return result
 9 ?
10 print(message_show(add))

思考:这种方式做到了业务功能的分离,但是 add 函数的传参是一个问题,这种方式很不灵活,继续改进。

代码改进,解决参数的问题:

 1 def add(x, y):
 2     return x+y
 3 ?
 4 def message_show(fn, *args):
 5     print(*args)
 6     print("begin...call add")
 7     result = fn(*args)
 8     print("end...{} + {}".format(*args))
 9     return result
10 ?
11 print(message_show(add, 4, 5))

至此,成功做到了新增业务功能的分离,可以看到并没有在原来的 add 函数中加入任何的代码,从而增强了它显示信息的功能。

二、装饰器的改造

(一)柯里化

要写出装饰器,首先要对函数进行柯里化。柯里化的过程如下代码:

 1 def add(x, y):
 2     return x+y
 3 ?
 4 def message_show(fn):
 5     def wrapper(*args):
 6         print(*args)
 7         print("begin...call add")
 8         result = fn(*args)
 9         print("end...{} + {}".format(*args))
10         return result
11     return wrapper
12 ?
13 print(message_show(add)(4, 5))

思考:message_show(add)(4, 5)这种调用方式能不能改造成add(4, 5),这种调用方法是不是就看不出调用过 message_show 函数了,而是直接调用的 add 函数,继续改造。

(二)装饰器定义

代码改造,柯里化之后,就可以使用装饰器的语法糖:

 1 def message_show(fn):
 2     def wrapper(*args):
 3         print(*args)
 4         print("begin...call add")
 5         result = fn(*args)
 6         print("end...{} + {}".format(*args))
 7         return result
 8     return wrapper
 9 ?
10 @message_show  # 等效于 add = message_show(add)
11 def add(x, y):
12     return x+y
13 ?
14 print(add(4, 5))

注意:装饰函数的代码,必须要定义在被装饰函数的代码之前。

至此,一个简单的装饰器就出来了。

三、无参装饰器

(一)无参装饰器的理解

  • 装饰器本身就是一个函数,而且是一个高阶函数

  • 它的形参是一个函数,ps:要被装饰的函数

  • 它的返回值是一个函数

  • 可以使用 @function_name 的方式调用

(二)装饰器的例子

 1 def fn_strengthen(fn):
 2     def wrapper(*args, **kwargs):
 3         print("========start=======")
 4         print("args = {}, kwargs = {}".format(args, kwargs))
 5         result = fn(*args, **kwargs)
 6         print(result)
 7         print("========end=========")
 8         return result
 9     return wrapper
10 ?
11 @fn_strengthen  # add = fn_strengthen(add)
12 def add(x, y):
13     print("==========call add=============")
14     return x + y
15 ?
16 add(4, y=5)

(三)装饰器的副作用

 1 def fn_strengthen(fn):
 2     def wrapper(*args, **kwargs):
 3         print("========start=======")
 4         print("args = {}, kwargs = {}".format(args, kwargs))
 5         result = fn(*args, **kwargs)
 6         print(result)
 7 ?
 8         print("========end=========")
 9         return result
10     return wrapper
11 ?
12 @fn_strengthen  # add = fn_strengthen(add)
13 def add(x, y):
14     """This is a function to add"""
15     print("==========call add=============")
16     return x + y
17 ?
18 print("name = {}, doc = {}".format(add.__name__, add.__doc__))  # name = wrapper, doc = None

注意:可以看出被装饰函数的属性都被替换了,这种问题应该如何解决?

代码改进,再定义一个函数,用来记录被装饰函数的属性:

 1 def fn_strengthen(fn):
 2     def wrapper(*args, **kwargs):
 3         print("========start=======")
 4         print("args = {}, kwargs = {}".format(args, kwargs))
 5         result = fn(*args, **kwargs)
 6         print(result)
 7 ?
 8         print("========end=========")
 9         return result
10     add_property(fn, wrapper)
11     return wrapper
12 ?
13 def add_property(fn, wrapper):
14     wrapper.__name__ = fn.__name__
15     wrapper.__doc__ = fn.__doc__
16 ?
17 @fn_strengthen  # add = fn_strengthen(add)
18 def add(x, y):
19     """This is a function to add"""
20     print("==========call add=============")
21     return x + y
22 ?
23 print("name = {}, doc = {}".format(add.__name__, add.__doc__))

上面的方法解决了属性被覆盖的问题,考虑到add_property这个函数的通用性,可以将这个复制属性的函数也改造成装饰器函数,只不过是带参的装饰器,代码改造如下:

 1 def add_property(fn):
 2     def _copy(wrapper):
 3         wrapper.__name__ = fn.__name__
 4         wrapper.__doc__ = fn.__doc__
 5         return wrapper
 6     return _copy
 7 ?
 8 def fn_strengthen(fn):
 9     @add_property(fn)  # wrapper = add_property(fn)(wrapper)
10     def wrapper(*args, **kwargs):
11         print("========start=======")
12         print("args = {}, kwargs = {}".format(args, kwargs))
13         result = fn(*args, **kwargs)
14         print(result)
15 ?
16         print("========end=========")
17         return result
18     return wrapper
19 ?
20 @fn_strengthen  # add = fn_strengthen(add)
21 def add(x, y):
22     """This is a function to add"""
23     print("==========call add=============")
24     return x + y
25 ?
26 print("name = {}, doc = {}".format(add.__name__, add.__doc__))

四、带参装饰器

(一)带参装饰器的引入

有一个需求,获取函数的执行时长,对时长超过阈值的函数记录一下:

 1 import datetime, time
 2 ?
 3 def add_property(fn):
 4     def _copy(wrapper):
 5         wrapper.__name__ = fn.__name__
 6         wrapper.__doc__ = fn.__doc__
 7         return wrapper
 8     return _copy
 9 ?
10 def time_detection(timeout):
11     def fn_strengthen(fn):
12         @add_property(fn)  # wrapper = add_property(fn)(wrapper)
13         def wrapper(*args, **kwargs):
14             print("========start=======")
15             start = datetime.datetime.now()
16             result = fn(*args, **kwargs)
17             print(result)
18             delta = (datetime.datetime.now() - start).total_seconds()
19             print("so slow") if delta > timeout else print("so fast")
20             print("========end=========")
21             return result
22         return wrapper
23     return fn_strengthen
24 ?
25 @time_detection(5)  # add = time_detection(5)(add)
26 def add(x, y):
27     """This is a function to add"""
28     print("==========call add=============")
29     time.sleep(6)
30     return x + y
31 ?
32 add(4, 5)

(二)带参装饰器的理解

  • 带参装饰器本身是一个函数,而且是一个高阶函数

  • 它的形参可以是一个函数,返回值是一个不带参数的装饰器函数

  • 使用 @function_name(参数) 方式调用

  • 可以看做是在装饰器的外层又加了一层函数

(三)带参装饰器的例子

将上一个例子中的记录时长的功能提取出来,能不能定义成一个参数来灵活的控制输出,代码改造如下:

 1 import datetime, time
 2 ?
 3 def add_property(fn):
 4     def _copy(wrapper):
 5         wrapper.__name__ = fn.__name__
 6         wrapper.__doc__ = fn.__doc__
 7         return wrapper
 8     return _copy
 9 ?
10 def time_detection(timeout, func=lambda name, delta:print("{} spend {}s".format(name, delta))):
11     def fn_strengthen(fn):
12         @add_property(fn)  # wrapper = add_property(fn)(wrapper)
13         def wrapper(*args, **kwargs):
14             print("========start=======")
15             start = datetime.datetime.now()
16             result = fn(*args, **kwargs)
17             print(result)
18             delta = (datetime.datetime.now() - start).total_seconds()
19             if delta > timeout:
20                 func(fn.__name__, delta)
21             print("========end=========")
22             return result
23         return wrapper
24     return fn_strengthen
25 ?
26 @time_detection(5)  # add = time_detection(5)(add)
27 def add(x, y):
28     """This is a function to add"""
29     print("==========call add=============")
30     time.sleep(6)
31     return x + y
32 ?
33 add(4, 5)

五、functools模块

这个模块就省了自己重新写复制属性的那个函数,functools.update_wrapper(包装函数, 原函数)使用这个模块来改造前面获取原函数属性的函数:

 1 import datetime, time, functools
 2 ?
 3 def time_detection(timeout, func=lambda name, delta:print("{} spend {}s".format(name, delta))):
 4     def fn_strengthen(fn):
 5         def wrapper(*args, **kwargs):
 6             print("========start=======")
 7             start = datetime.datetime.now()
 8             result = fn(*args, **kwargs)
 9             print(result)
10             delta = (datetime.datetime.now() - start).total_seconds()
11             if delta > timeout:
12                 func(fn.__name__, delta)
13             print("========end=========")
14             return result
15         return functools.update_wrapper(wrapper, fn)
16     return fn_strengthen
17 ?
18 @time_detection(5)  # add = time_detection(5)(add)
19 def add(x, y):
20     """This is a function to add"""
21     print("==========call add=============")
22     time.sleep(6)
23     return x + y
24 ?
25 add(5, 6), add.__name__, add.__doc__

还可以使用这个模块的装饰器的方法来复制属性,@functools.wraps(原函数)

 1 import datetime, time, functools
 2 
 3 def time_detection(timeout, func=lambda name, delta:print("{} spend {}s".format(name, delta))):
 4     def fn_strengthen(fn):
 5         @functools.wraps(fn)
 6         def wrapper(*args, **kwargs):
 7             print("========start=======")
 8             start = datetime.datetime.now()
 9             result = fn(*args, **kwargs)
10             print(result)
11             delta = (datetime.datetime.now() - start).total_seconds()
12             if delta > timeout:
13                 func(fn.__name__, delta)
14             print("========end=========")
15             return result
16         return wrapper
17     return fn_strengthen
18 
19 @time_detection(5)  # add = time_detection(5)(add)
20 def add(x, y):
21     """This is a function to add"""
22     print("==========call add=============")
23     time.sleep(6)
24     return x + y
25 
26 print(add(5, 6), add.__name__, add.__doc__)

 

以上是关于装饰器的主要内容,如果未能解决你的问题,请参考以下文章

Python面向对象学习之八,装饰器

thymeleaf 片段渲染后重新加载 javascript

代码缺乏装饰?使用ts装饰器来装饰你的代码

代码缺乏装饰?使用ts装饰器来装饰你的代码

代码缺乏装饰?使用ts装饰器来装饰你的代码

代码缺乏装饰?使用ts装饰器来装饰你的代码