【python实用特性】- 装饰器


装饰器的核心理念——在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实用特性】- 迭代、可迭代对象、迭代器

[3] 【python实用特性】- 列表生成式

[4] 【python实用特性】- yield生成器

[5] Python如何爬取动态网页数据

[6] Python+selenium实现自动爬取实例

[7] python爬取豆瓣Top250-改进版

[8]【Scrapy爬取实例】- 爬取链家网指定城市二手房源信息

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章