修饰器

简介

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# coding:utf-8

# 修饰器 就是一个嵌套函数模版,对输入的参数函数进行加工,再以一个新的函数输出。

def check_str(func):
print('func :',func)
def inner(*args,**kwargs): # 这里inner获得的是参数函数func 的参数。
print('args:',args,' ','kwargs: ',kwargs)
func(*args,**kwargs) # 这里把参数 还给参数函数。
result = func(*args,**kwargs)
if result == 'ok':
return 'result is %s'%result
else:
return 'result is failed %s'%result
return inner # 但是注意return的不是inner()而是inner,从这里可以看出修饰器不是简单的函数嵌套,而是“闭包”,下面有讲解。

@check_str # test作为参数函数使用修饰器check_str 的格式规范。另一种用法见下方#引用部分。
def test(data):
return data


result = test('no')
print(result)

print('~~~~~~~~~~~~~~~~~~~~~~~')

def test2(data):
return data

test2 = check_str(test2) #另一种使用修饰器的方式。不管有无参数都这样写。
#因为这是方法的 定义过程。

result = test2(data = 'ok')
print(result)

闭包的理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 简单的方法嵌套
def outer():
x = 1
def inner():
print(x)
inner() # 注意这里有括号,直接被调用

outer() # 1

# 闭包
def outer(x):
def inner():
print(x)

return inner #没括号,不被直接调用
closure = outer(1) # closure就是一个闭包
closure() # 1

# 同样是嵌套函数,只是稍改动一下,把局部变量 x 作为参数了传递进来,嵌套函数不再直接在函数里被调用,而是作为返回值返回,这里的 closure就是一个闭包,本质上它还是函数,闭包是引用了自由变量(x)的函数(inner)。

​ 更多装饰器 的学习,可以参考,本文下面都是 别人写的内容,总结的非常浅显易懂。

https://www.cnblogs.com/willsdu/p/16422647.html

以下均为参考的内容

现实问题

在实际的项目中,经常会遇到需要在原有项目上加一些功能,但是这些功能可能很快被删除掉,如何实现这种要求呢?下面举例一个简单的函数

1
2
def foo():
print('hello')

如果想打印该函数的执行时间:

1
2
3
4
5
6
7
from time import time

def foo():
t1 = time()
print('hello')
t2 = time()
print(t2 - t1)

这种在原有函数里面加代码,也不符合开闭原则,那么我可以新增加一个函数:

1
2
3
4
5
6
7
8
9
from time import time

def run_time(func):
def wrap():
t1 = time()
func()
t2 = time()
print(t2 - t1)
return wrap

这么写的确不用修改原来的代码,但是新增函数还是要被调用的。Python参照Java,提供了注解来优化该场景,上述场景可以简写如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from time import time


def run_time(func):
def wrap():
t1 = time()
func()
t2 = time()
print(t2 - t1)
return wrap

@run_time
def foo():
print('hello')

修饰函数

Python注解修饰函数时,函数定义的不同,注解的实现也不同

无参数无返回

上面的例子就是无参数无返回值

无参数有返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from time import time

def run_time(func):
def wrap():
t1 = time()
r = func()
t2 = time()
print(t2-t1)
return r
return wrap

@run_time
def foo():
return 'hello'

print(foo())

有参数无返回

有参数无返回这种情况较少

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from time import time

def run_time(func):
def wrap(a, b):
t1 = time()
func(a, b)
t2 = time()
print(t2 - t1)

return wrap

@run_time
def foo(a, b):
print(a + b)

foo(2, 45)

有参数有返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from time import time

def run_time(func):
def wrap(a, b):
t1 = time()
r=func(a, b)
t2 = time()
print(t2 - t1)
return r
return wrap

@run_time
def foo(a, b):
return a + b

print(foo(2, 45))

装饰器带参数

有时候装饰器可能需要一些参数,这个场景更复杂,具体实现也就是多层套娃来容纳不同的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def namedDecorator(name):
def run_time(func):
def wrap(a, b):
print('this is:{}'.format(name))
r = func(a, b)
return r
return wrap
return run_time

@namedDecorator("装饰器带参数")
def foo(a, b):
return a + b

print(foo(2, 45))

消除副作用

上述的装饰后,foo函数已经不是原来的foo了,已经是被装饰后的函数了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def namedDecorator(name):
def run_time(func):
def wrap(a, b):
'''my decorator'''
print('this is:{}'.format(name))
r = func(a, b)
return r
return wrap
return run_time

@namedDecorator("装饰器带参数")
def foo(a, b):
"""example docstring"""
return a + b

print(foo(2, 45))
print(foo.__name__, foo.__doc__)

返回

1
2
3
this is:装饰器带参数
47
wrap my decorator

使用@wraps装饰器可以消除这种影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from functools import wraps


def namedDecorator(name):
def run_time(func):
@wraps(func)
def wraper(a, b):
'''my decorator'''
print('this is:{}'.format(name))
r = func(a, b)
return r

return wraper

return run_time

@namedDecorator("装饰器带参数")
def foo(a, b):
"""example docstring"""
return a + b


print(foo(2, 45))
print(foo.__name__, foo.__doc__)

执行结果

1
2
3
this is:装饰器带参数
47
foo example docstring

多个装饰器

如果想要多个装饰器装饰同一个函数会怎样呢?

执行顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from time import time


def run_time_1(func):
def wrap(a, b):
r = func(a, b)
print('run_time_1')
return r
return wrap

def run_time_2(func):
def wrap(a, b):
r = func(a, b)
print('run_time_2')
return r
return wrap

@run_time_1
@run_time_2
def foo(a, b):
return a + b

print(foo(2, 45))

返回结果:

1
2
3
run_time_2
run_time_1
47

结论:离被修饰函数最近的一侧先执行

包裹形式

我们在上面的代码上加几行代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from time import time


def run_time_1(func):
def wrap(a, b):
"""run_time_1 docstring"""
print(func.__name__, func.__doc__)
r = func(a, b)
print('run_time_1')
return r
return wrap

def run_time_2(func):
def wrap(a, b):
"""run_time_2 docstring"""
r = func(a, b)
print('run_time_2')
return r
return wrap

@run_time_1
@run_time_2
def foo(a, b):
"""foo docstring"""
return a + b

print(foo(2, 45))
print(foo.__name__, foo.__doc__)

输出结果:

1
2
3
4
5
wrap run_time_2 docstring
run_time_2
run_time_1
47
wrap run_time_1 docstring

结论:这是洋葱一样,一层包一层的结构
当然这可以使用@wrap消除影响,就不加代码了

修饰类

装饰器还可以装饰类,本质不变,都是执行一段自定义代码后再返回,可以用套娃,洋葱打比方

1
2
3
4
5
6
7
8
9
10
11
def decorator(cls):
print("这里可以写被装饰类新增的功能")
return cls

@decorator
class A(object):
def __init__(self):
pass

def test(self):
print("test")

执行结果

1
这里可以写被装饰类新增的功能

与装饰函数不同的是,被装饰的函数需要调用下,被装饰的类,不需要调用,申明阶段即可发挥作用