文章目录
装饰器的核心理念——在python中,一切皆对象,所以,
函数也是一个对象
!
1、装饰器是什么?有何好处?
- 定义
简单来说,装饰器
,是在不更改程序主体内容的前提下,对其进行功能扩展的解决方案。常用于权限验证、数据校验、日志打印等需求。 - 优点
(1) 不用更改程序主体。这意味着主业务逻辑并不会受到影响,减少了修改成本及扩展风险。
(2) 可对多个有同样需求的对象进行装饰,提高了代码复用性。
2、理解装饰器的前提-高阶函数与嵌套函数
装饰器的本质是一个函数(类装饰器也是通过__call__函数来实现)
,不过与一般函数不同的是,它是一个高阶嵌套函数
,所以在真正理解装饰器之前,得先明白什么是高阶函数
和嵌套函数
2.1 高阶函数
如最开始所说,函数也是一个对象,这意味着可以将它赋给一个变量,而函数本身也可以接受变量作为参数,所以所谓的高阶函数,便是 将一个函数作为另一个函数的参数的函数
。
文字有点绕,看个实例
#func:用于接收传来的函数对象
def high_func(func):
print('我是一个高阶函数,我叫:%s'%high_func.__name__)
print('我是传来的函数对象,我叫:%s'%func.__name__)
def normal_func():
print('我是一个普通函数')
#查看normal_func的类型
print(type(normal_func))
#调用high_func函数,将normal_func函数作为参数
high_func(normal_func)
输出:
<class 'function'>
我是一个高阶函数,我叫:high_func
我是传来的函数对象,我叫:normal_func
由结果可知,normal_func
是一个函数对象,作为参数赋给了变量func
,此时high_func
就成了一个高阶函数。
2.2 嵌套函数
这个很好理解,就像穿衣服一样的,一件套一件。程序中,就是一个函数中包含一个或多个函数
,可以不停嵌套,但建议不要超过3个。
实例如下:
#嵌套函数定义
def first():
print('我是第一层')
def second():
print('我是第二层')
def thirst():
print('我是第三层')
return thirst
return second
#调用,从外到内,层层调用
f = first()()()
输出:
我是第一层
我是第二层
我是第三层
3、函数装饰器
函数装饰器可根据参数的需求分为以下三种。
3.1 无参数
这种装饰器最为简单,实例如下:
# 无参数装饰器
def decorator1(func):
def wrapper():
print('我在被装饰函数之前执行')
func()
print('我在被装饰函数之后执行')
return wrapper
@decorator1 #调用装饰器 @参数器函数名
def f():
print('我是被装饰的函数')
输出:
我在被装饰函数之前执行
我是被装饰的函数
我在被装饰函数之后执行
如上,我们定义了一个简单的装饰器,装饰器decorator1与被装饰的函数f都无参数
。它的功能是在被装饰的函数f
执行前后各打印一句话。
装饰原理: 上面代码中,使用了@
语法糖来调用装饰器。其原理是将被装饰的函数f
作为参数,传入装饰器函数decorate1
中,之后将返回的wrapper
函数重新赋给f
,即 f = decorator1(f)
3.2 被装饰的函数带参数
# 被装饰函数带有参数的装饰器
def decorator2(func):
def wrapper(*args, **kwargs):
print('我是被装饰函数带有参数的装饰器')
return func(*args, **kwargs) #因为原函数f有返回值,所以此处要return
return wrapper
@decorator2
def f(name):
print('我是被装饰的函数,我有参数name,值为:%s' % name)
return name
print('返回值:',f(name='jxc'))
print('被装饰的函数是%s' % f.__name__)
输出:
我是被装饰函数带有参数的装饰器
我是被装饰的函数,我有参数name,值为:jxc
被装饰的函数是wrapper
如上,被装饰的函数f
带有参数name
,所以装饰器的内部嵌套了一个函数wrapper
来接受参数。
但此时出现了一个问题,被装饰的函数明明是f
,怎么打印出来变成了wrapper
呢?很简单,注意这一句 return wrapper
,将内部的wrapper函数返回,再赋给了f,所以此时的f.__name__
变成了wrapper。
解决办法:使用functools 中的wraps装饰器 ,改写如下:
from functools import wraps
#被装饰函数带有参数的装饰器
def decorator2(func):
@wraps(func) #使用wraps装饰器保存原函数信息
def wrapper(*args,**kwargs):
print('我是被装饰函数带有参数的装饰器')
return func(*args, **kwargs)
return wrapper
@decorator2
def f(name):
print('我是被装饰的函数,我有参数name,值为:%s'%name)
return name
f(name='jxc')
print('被装饰的函数是%s'%f.__name__)
输出:
我是被装饰函数带有参数的装饰器
我是被装饰的函数,我有参数name,值为:jxc
被装饰的函数是f
3.3 装饰器带有参数
通过上面我们知道,两层嵌套的装饰器只能接收被装饰函数的参数。所以要想接收装饰器传来的参数,那就得再加一层
。
在3.2的基础上加个需求,根据成绩分数判断是否及格
#装饰器带有参数的装饰器
def decorator3(grade):
def get_func(func):
@wraps(func)
def wrapper(*args,**kwargs):
print('我是装饰器带有参数的装饰器')
words = '恭喜你,及格了' if grade>=60 else '没及格,下次努力'
print('本次考了%d分'%grade,words)
return func(*args, **kwargs)
return wrapper
return get_func
@decorator3(grade=60)
def f(name):
print('我是被装饰的函数,我有参数name,值为:%s'%name)
f(name='jxc')
print('被装饰的函数是%s'%f.__name__)
输出:
我是装饰器带有参数的装饰器
本次考了60分 恭喜你,及格了
我是被装饰的函数,我有参数name,值为:jxc
被装饰的函数是f
可见,此时的装饰器共有三层,最外层用来接收装饰器参数,中间层接收被装饰函数对象,最里层接收被装饰函数的参数。
4、类装饰器
顾名思义,类装饰器是通过类的形式(本质上还是通过__call__函数,将类变为一个函数来实现
)来进行功能扩展的,相比于函数装饰器而言,它具有高内聚
、可继承
等特点。
实例如下:
class decoratorTest():
def __init__(self,func):
self.func = func
def __call__(self,*args,**kwargs):
print('我是一个类装饰器,现在执行被装饰的函数:%s'%self.func.__name__)
print('传入的参数:',kwargs)
return self.func(*args,**kwargs)
@decoratorTest
def f(name):
print('我是被装饰的函数,我有参数name,值为:%s'%name)
f(name='jxc')
输出:
我是一个类装饰器,现在执行被装饰的函数:f
传入的参数: {'name': 'jxc'}
我是被装饰的函数,我有参数name,值为:jxc
__init__
接收被装饰的函数对象,__call__
进行逻辑处理,类装饰器这里不作深入研究。
5、多个装饰器的执行顺序
一个函数可以被多个装饰器同时装饰,实例如下:
#装饰器d1
def d1(func):
def warpper1():
print('洗脸')
func()
return warpper1
#装饰器d2
def d2(func):
def warpper2():
print('刷牙')
func()
return warpper2
#装饰器d3
def d3(func):
def warpper3():
print('起床')
func()
return warpper3
@d3
@d2
@d1
def f():
print('上班去喽')
f()
输出:
起床
刷牙
洗脸
上班去喽
如上,使用了d1、d2、d3
三个装饰器同时对函数f
进行装饰。
- 装饰顺序:
d1—>d2—>d3
- 执行顺序:
d3—>d2—>d1—>f
可见,执行顺序与装饰顺序是相反的,这是为什么呢?根据3.1提到的的装饰原理可以得到下列式子:f = d3(d2(d1(f)))
装饰步骤:
(1)将原函数f
作为参数传给d1
,此时d1中的func等于f
(2)再将d1
作为参数传给d2
,此时d2中的func等于d1
(3)再将d2
作为参数传给d3
,此时d3中的func等于d2
(4)最后将d3
的返回函数wrapper3
赋给原函数f
执行步骤:
(1)执行f()
之后,先执行d3的warpper3
,打印起床,之后调用d2
(2)d2的warpper2
打印刷牙,之后调用d1
(3) d1的wrapper1
打印洗脸,最后执行原函数f
(4) f
打印 上班去喽
小结: 需要先执行的装饰器放上面!
按照自己的理解写的,内容较多,有些地方的表述可能比较绕或不准确,欢迎留言讨论~
参考文章:
[1] 如何理解Python装饰器?- 知乎
[2] Python 装饰器执行顺序迷思
博主其他系列文章:
[1] 【python实用特性】-切片
[2] 【python实用特性】- 迭代、可迭代对象、迭代器
[5] Python如何爬取动态网页数据