目录
一、装饰器概述
二、装饰器的入门
1.什么是闭包
2.什么是装饰器
3.闭包与装饰器的区别
4.为何要用装饰器?
5.适用场景
?三、如何使用装饰器
1.通过案例了解装饰器的用法
2.Python装饰器执行顺序详解
四、装饰器传参?
五、装饰器返回值?
?六、通用装饰器
?七、装饰器带参数
一、装饰器概述
装饰器是一个函数,该函数是用来为其他函数添加额外的功能,就是拓展原来函数功能的一种函数。
二、装饰器的入门
1.什么是闭包
要掌握装饰器先得理解闭包,掌握了闭包以后再学装饰器就很容易了。
闭包就是外部函数中定义一个内部函数,内部函数引用外部函数中的变量,外部函数的返回值是内部函数。闭包是将函数内部和函数外部连接起来的桥梁。
举例:比如我们调用一个带有返回值的函数x,此时函数x为我们返回一个函数y,这个函数y,就被称为闭包。
# 闭包
def out(i): # 一个外层函数
def inner(j): # 内层函数
return i*j
return inner
闭包定义条件:
- 必须有一个内嵌函数
- 内嵌函数必须引用外部函数中的变量
- 外部函数的返回值必须是内嵌函数
?案例:函数test_in就是闭包
def test(number):
print("--1--")
def test_in(number2):
print("--2--")
print(number+number2)
print("--3--")
return test_in
ret = test(100) #先执行第3行,输出"--1--";再执行第5行,函数名(test_in),不执行函数内部;然后执行第10行,输出"--3--";然后返回(test_in)函数给(test)函数,赋值给ret对象。
print("-"*30) #输出”------------------------------“
ret(1) #执行(test_in)函数,执行第6行,输出"--2--";执行第7行,输出"101"(number=100,number2=1)。
ret(100) #执行(test_in)函数,执行第6行,输出"--2--";执行第7行,输出"200"(number=100,number2=100)。
ret(200) #执行(test_in)函数,执行第6行,输出"--2--";执行第7行,输出"300"(number=100,number2=200)。
?输出结果:
--1--
--3--
------------------------------
--2--
101
--2--
200
--2--
300
运行分析:先调用外部函数test传入默认值number,用ret去指向返回函数内部的引用。后面在调用ret的时候,就会在调用外面函数的基础上进行计算。
2.什么是装饰器
装饰器就是装饰、装修的意思,不改变原有程序的功能。比如,我家有一个房子,如果不隔音,我在墙上加一层隔音板,而不是重新再建一座房子。这样一来,房子还是原来的,只是增加了一个隔音板(装饰器),实现了房子隔音的功能。
在程序中也是一样,不会对原来的函数造成变化,还要添加新的功能,调用函数时的接口没有变化。
装饰器可以基于函数实现,也可以基于类实现,其使用方式基本是固定的。它的基本步骤为:
- 定义装饰函数(类)
- 定义业务函数
- 在业务函数上一行添加@装饰函数名/类名
案例:(需求:在不动原函数的基础上增加新的功能)
def w1(func):
"""装饰器函数"""
def inner():
func()
print("这是添加的新功能")
return inner
@w1 # 等同于调用函数的时候上方加上:f1 = w1(f1)
def f1():
"""业务函数"""
print("---f1---")
@w1 # 等同于调用函数的时候上方加上:f2 = w1(f2)
def f2():
"""业务器函数"""
print("---f2---")
f1()
f2()
输出结果:
---f1---
这是添加的新功能
---f2---
这是添加的新功能
3.闭包与装饰器的区别
闭包传递的是?变量,而装饰器传递的是?函数,除此之外没有任何区别,或者说装饰器是闭包的一种,它只是?传递函数的闭包。
4.为何要用装饰器?
开放封闭原则 开放:指的是对拓展功能是开放的 封闭:指的是对修改源代码是封闭的
装饰器就是在不修改被装饰器对象源代码以及调用方式的前提下,为被装饰对象添加新功能。
5.适用场景
函数在执行前后定义一些功能。
?三、如何使用装饰器
1.通过案例了解装饰器的用法
案例:
def w1(fn):
"""装饰器函数"""
print("---正在装饰---")
def inner():
print("---正在验证---")
fn()
return inner
@w1 # 只要Python解释器执行到这个代码,就开始自动进行装饰,而不是等到调用的时候才装饰
def f1():
"""业务函数"""
print("---2---")
return "hello python"
输出结果:
---正在装饰---
运行分析:
代码执行到@w1就开始装饰了,我们并没有调用函数f1都输出了“---正在装饰---”,我们调用的是装饰后的结果。
案例:
def w1(fn):
"""装饰器函数"""
print("---正在装饰1---")
def inner():
print("---正在验证---")
fn()
return inner
def w2(fn):
"""装饰器函数"""
print("---正在装饰2---")
def inner():
print("---正在验证---")
fn()
return inner
@w1
@w2
def f1():
"""业务函数"""
print("---3---")
?输出结果:
---正在装饰2---
---正在装饰1---
运行分析:
@w1在最上面,下面需要是一个函数,可下面是@w2,必须等@w2装饰完再装饰@w1,所以先输出?---正在装饰2---,再输出?---正在装饰1---。?
2.Python装饰器执行顺序详解
2.1案例执行
?案例:
def decorator_a(func):
print ('Get in decorator_a')
def inner_a(*args, **kwargs):
print ('Get in inner_a')
return func(*args, **kwargs)
return inner_a
def decorator_b(func):
print ('Get in decorator_b')
def inner_b(*args, **kwargs):
print ('Get in inner_b')
return func(*args, **kwargs)
return inner_b
@decorator_b
@decorator_a
def f(x):
print ('Get in f')
return x * 2
f(1)
输出结果:
Get in decorator_a
Get in decorator_b
Get in inner_b
Get in inner_a
Get in f
?运行分析:
上面代码先定义两个函数:?decotator_a, decotator_b , 这两个函数实现的功能是,接收一个函数作为参数,然后返回创建的另一个函数;
在这个创建的函数里,调用接收的函数(文字比代码绕人)。最后定义的函数 f ?采用上面定义的decotator_a, decotator_b 作为装饰函数。
在当我们以1为参数,调用装饰后的函数 f 后,?decotator_a, decotator_b ?的顺序是什么呢(这里为了表示函数执行的先后顺序,采用打印输出的方式来查看函数的执行顺序)?
如果不假思索,根据自下而上的原则来判断地话,先执行?decorator_a ?再执行?decorator_b ?, 那么会先输出?Get in decotator_a ,?Get in inner_a ?再输出?Get in decotator_b ,?Get in inner_b ?。然而事实并非如此。
?2.2函数和函数调用的区别
为什么是先执行inner_b ?再执行inner_a ?呢?为了彻底看清上面的问题,得先分清两个概念:函数和函数调用。
上面的例子中 f?称之为函数, f(1) 称之为函数调用,后者是对前者传入参数进行求值的结果。在Python中函数也是一个对象,所以?函数f 是指代一个函数对象,它的值是函数本身, f(1) 是对函数的调用,它的值是调用的结果,这里的定义下 f(1) 的值为2。同样地,拿上面的decorator_a 函数来说,它返回的是个函数对象inner_a ?,这个函数对象是它内部定义的。在inner_a ?里调用了函数 func?,将?函数func?的调用结果作为值返回。
2.3装饰器函数在被装饰函数定义好后立即执行
当装饰器装饰一个函数时,究竟发生了什么。现在简化我们的例子,假设是下面这样的:
def decorator_a(func):
print ('Get in decorator_a')
def inner_a(*args, **kwargs):
print ('Get in inner_a')
return func(*args, **kwargs)
return inner_a
@decorator_a
def f(x):
print ('Get in f')
return x * 2
?解读:
@decorator_a #等同于调用函数的时候上方加上:f=decorator_a(f)
def f(x):
print ('Get in f')
return x * 2
# 相当于
def f(x):
print ('Get in f')
return x * 2
f = decorator_a(f)
?运行分析:
当解释器执行这段代码时,?decorator_a ?已经调用了,它把函数名f 作为参数传递给形参fun, 返回它内部生成的一个函数inner_a,所以此后 f 指代的是?decorater_a ?里面返回的?inner_a 。所以当以后调用 函数f 时,实际上相当于调用?inner_a ,传给函数?f 的参数会传给函数inner_a , 在调用函数inner_a ?时,会把接收到的参数(函数名 f)传给inner_a 里的 形参func=f ?,最后返回的是 调用函数f 的值,所以在最外面看起来就像是直接调用函数 f 一样。
2.4案例执行顺序分析
当理清上面两方面概念时,就可以清楚地看清最原始的例子中发生了什么。 当解释器执行下面这段代码时,实际上按照从下到上的顺序已经依次调用了?decorator_a 和?decorator_b ,这是会输出对应的?Get in decorator_a ?和?Get in decorator_b 。 这时候函数f 已经相当于 函数decorator_b 里的 函数inner_b 。但因为 函数 f 并没有被调用,所以 函数inner_b ?并没有被调用,依次类推?函数inner_b 内部的?函数inner_a 也没有被调用,所以?Get in inner_a ?和?Get in inner_b 也不会被输出。
案例:
def decorator_a(func):
print ('Get in decorator_a')
def inner_a(*args, **kwargs):
print ('Get in inner_a')
return func(*args, **kwargs)
return inner_a
def decorator_b(func):
print ('Get in decorator_b')
def inner_b(*args, **kwargs):
print ('Get in inner_b')
return func(*args, **kwargs)
return inner_b
@decorator_b
@decorator_a
def f(x):
print ('Get in f')
return x * 2
f(1)
输出结果:?
Get in decorator_a
Get in decorator_b
Get in inner_b
Get in inner_a
Get in f
然后第21行,当我们对函数? f 传入参数1进行调用时,函数inner_b 被调用了,它会先打印?Get in inner_b ,然后在函数?inner_b ?内部调用了函数inner_a, 所以会再打印?Get in inner_a , 然后在函数?inner_a ?内部调用的原来的函数 f, 并且将结果作为最终的返回值。这时候你该知道为什么输出结果会是那样,以及对装饰器执行顺序实际发生了什么有一定了解了吧。
2.5案例执行顺序总结
-多个装饰器执行的顺序,是按照从下到上的顺序执行装饰器,再执行函数本身。
-装饰器的外函数和内函数之间的语句是没有装饰到目标函数上的,而是在装载装饰器时的附加操作。?
15~19行是装载装饰器的过程,相当于执行了f=decorator_b(decorator_a(f)):
- 此时先执行decorator_a(f),结果是输出 Get in decorator_a,将形参func指向函数f、并返回函数
inner_a; - 然后执行函数 decorator_b(inner_a),结果是输出 Get in decorator_b,将形参 func指向函数inner_a、并返回函数inner_b;
- 函数 f本身相当于函数inner_b。
21行则是实际调用被装载的函数,用函数名?inner_b替代了函数名f。
- 这时实际上执行的是函数inner_b;
- 运行到函数func()时执行函数inner_a;
- 再运行到函数 func()时执行未修饰的函数f。
四、装饰器传参?
案例:传递2个参数
def w1(fn):
"""装饰器函数"""
print("---正在装饰---")
def inner(a, b): # 如果a, b没有定义,那么会导致19行代码调用失败
print("---正在验证---")
fn(a, b) # 如果没有把a, b当实参进行传递,那么会导致调用13行的函数失败
return inner
@w1
def f1(a, b):
"""业务函数"""
print(a + b)
f1(10, 20)
输出结果:
---正在装饰---
---正在验证---
30
案例:不定长参数
def w1(fn):
"""装饰器函数"""
print("---正在装饰---")
def inner(*args, **kwargs): # 如果a, b没有定义,那么会导致19行代码调用失败
print("---正在验证---")
fn(*args, **kwargs) # 如果没有把a, b当实参进行传递,那么会导致调用13行的函数失败
return inner
@w1
def f1(a, b):
"""业务函数"""
print(a + b)
@w1
def f2(a, b, c):
"""业务函数"""
print(a + b + c)
f1(10, 20)
f2(10, 20, 30)
输出结果:
---正在装饰---
---正在装饰---
---正在验证---
30
---正在验证---
60
五、装饰器返回值?
案例 :
def w1(fn):
"""装饰器函数"""
print("---正在装饰---")
def inner():
print("---正在验证---")
ret = fn() # 保存返回来的字符串
return ret # 把字符串返回到20行的调用处
return inner
@w1
def test():
"""业务函数"""
print("---test---")
return "这是原函数返回值"
ret = test() # 需要用参数来接收返回值
print(ret)
输出结果:
---正在装饰---
---正在验证---
---test---
这是原函数返回值
?六、通用装饰器
?案例:
def w1(fn):
"""装饰器函数"""
def inner(*args, **kwargs):
print("---记录日志---")
ret = fn(*args, **kwargs) # 保存返回来的字符串
return ret # 把字符串返回到20行的调用处
return inner
@w1
def test1():
"""不带返回值"""
print("---test1---")
@w1
def test2():
"""带返回值"""
print("---test2---")
return "这是原函数返回值"
@w1
def test3(a):
"""业务函数"""
print("---test3中的数据:%d---" % a)
ret1 = test1()
print(ret1)
ret2 = test2()
print(ret2)
ret3 = test3(10)
print(ret3)
输出结果:
---记录日志---
---test1---
None
---记录日志---
---test2---
这是原函数返回值
---记录日志---
---test3中的数据:10---
None
?七、装饰器带参数
案例:
def func_arg(arg):
def func(funtionName):
def func_in():
print("输出给装饰器传入的参数:%s" % arg)
if arg == "hello":
funtionName()
funtionName()
else:
funtionName()
return func_in
return func
@func_arg("hello")
def test():
print("---test---")
@func_arg("haha")
def test2():
print("---test2---")
test()
test2()
?输出结果:
输出给装饰器传入的参数:hello
---test---
---test---
输出给装饰器传入的
?运行分析:
- 先执行func_arg("hello")函数,这个函数return的结果是func这个函数的引用;
- 执行@func;
- 使用@func对函数test进行装饰
|