Python 装饰器详解

装饰器简介

python装饰器本质上就是一个函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,装
饰器的返回值也是一个函数对象
python装饰器的特点:
		一 不会改变原有函数的调用方式。
	    二 不会改变原有函数的代码内容

简易版本装饰器

在介绍简易版本装饰器之前,先介绍一下一些方法,为了满足示例中的功能。

import time  # 导入模块
time.time()  # 调用模块方法,此方法是返回一个时间戳,1970年1月1日至今的秒数
time.sleep()  # 让程序等待多少秒后执行
获取函数运行时间

	import time  # 导入模块
	
	def index():  # 定义函数
	    time.sleep(3)  # 睡眠 3 秒
	    print('from index')
	
	start_time = time.time()  # 获取程序运行前的时间戳
	index()  # 运行函数
	end_time = time.time()  # 获取函数运行后的时间戳
	print(end_time - start_time)  # 打印函数运行的时间

上述代码可以得出函数执行的时间,但是如果是多个函数需要获取运行时间呢?
每一个函数都使用一遍就会导致代码冗余
没有装饰器情况

	import time  # 导入模块
	
	def index():  # 定义需要获取运行时间的函数
	    time.sleep(3)
	    print('from index')
	    
	def get_time(func):  # 定义获取时间的函数,相当于一个同用的工具
	    start_time = time.time()
	    func()
	    end_time = time.time()
	    print(end_time - start_time)
	
	get_time(index)  # 将函数名当作实参传入 get_time() 中,并在其函数体里调用

这种方法也可以实现多个函数获取运行时间,但是这种不属于装饰器的方法。
使用装饰器的情况

	def index():
	    time.sleep(3)
	    print('from index')
	
	def outer(func):
	    def inner():
	        start_time = time.time()
	        func()
	        end_time = time.time()
	        print(end_time - start_time)
	    return inner
	
	index = outer(index)
	index()

(1) 上述代码运行的时候,首先定义函数 index() 和 函数outer(),但不执行代码内容。
(2) 然后看赋值语句右边,将函数名 index 作为实参传入函数 outer() ,此时运行函数 outer()
(3) 运行函数 outer()后,在函数里先定义内层函数 inner() 但不执行,返回值 inner 变量名
(4) 将返回值赋值给变量 index ,这个 index 可不是函数名的 index ,是新创建的一个全局同名变量。
此时相当于变量名 inner 
(5) 执行函数 index() ,由于 index 和 inner 的内存地址指向相同,即相当于调用了函数 inner() 
(6) 函数inner()执行后,使用形参 func ,由于是函数名 index 为实参,即相当于执行了 index()函数
(7) 由函数inner()函数体代码就可以得出运行时间了。
(8) 在内层函数里使用外层函数的变量而不是全局变量,且没有改变原有代码内容。即装饰器。

进阶版本装饰器

在简易版本的装饰器中,实现了不影响函数调用方式和原有代码的情况下实现功能的添加,但是此时有缺陷,就是若需要添加功能的函数需要参数呢?

适应有参无参
考虑原有函数需要参数的情况

	import time
	
	def index(a, b):
	    time.sleep(1)
	    print('from index')
	
	def outer(func):
	    def inner(*args, **kwargs):
	        start_time = time.time()
	        func(*args, **kwargs)
	        end_time = time.time()
	        print(end_time - start_time)
	    return inner
	
	index = outer(index)  # index = inner
	index(1, 2)

(1) 原有函数若是要传参,不在装饰器里面修改参数的话会报错,装饰器是给多个函数装饰的,但是若有的函
数没有参数呢?如何能够同时满足有参函数和无参函数呢?还记得前面的一篇文章提及的*号和**号吗?
(2) 步骤和简易装饰器一样,不同的是在函数 inner() 中添加了 *args 和 **kwargs 用于接收任意数量
任意类型的参数。
(3) 当我们将返回值 inner 赋值给index后,调用函数 index() 并传入参数,其实是在调用函数inner()
(4) 真正的函数 index 其实是函数 inner() 里面的 func()
(5) 我们打印 index 后结果是<function outer.<locals>.inner at 0x0000022939BD0940>

完整版本装饰器

进阶的装饰器解决了函数有参无参的问题,还有一个问题就是,若是函数有返回值怎么办?完整版本里面可以解决这个问题

示例错误代码:

	import time
	
	def index(a, b):
	    time.sleep(1)
	    print('from index', a, b)
	    return 'XWenXiang'
	
	def outer(func):
	    def inner(*args, **kwargs):
	        start_time = time.time()
	        func(*args, **kwargs)
	        end_time = time.time()
	        print(end_time - start_time)
	    return inner
	
	index = outer(index)  # index = inner
	x = index(1, 2)
	print(x)


当被装饰函数有返回值的时候,由于我们重新定义的 index 也就是 inner 是没有返回值的,所以会返回一
个 None ,一看就是假的,所以我们要解决这个问题。

	import time
	
	def index(a, b):
	    time.sleep(1)
	    print('from index', a, b)
	    return 'XWenXiang'
		
	def outer(func):  # func = index
	    def inner(*args, **kwargs):
	        start_time = time.time()
	        res = func(*args, **kwargs)  # index(1, 2)
	        end_time = time.time()
	        print(end_time - start_time)
	        return res  # index
	    return inner
		
	index = outer(index)  # outer(index) = inner
	x = index(1, 2)  # x = inner(1, 2) = index
	print(x)

和上面的代码相比,我在函数 inner() 中定义了一个变量用于接收函数 func(*args, **kwargs) 的返回
值,也就是函数 index() 的返回值,然后把 res 当作函数 inner() 的返回值即可。

装饰器模板(拷贝使用即可)

装饰器有它的模板写法

def outer(func_name):  # func_name用于接收被装饰的对象(函数)
    def inner(*args, **kwargs):
        print('执行被装饰函数之前 可以做的额外操作')
        res = func_name(*args, **kwargs)  # 执行真正的被装饰函数
        print('执行被装饰函数之后 可以做的额外操作')
        return res  # 返回真正函数的返回值
    return inner

def index(a, b):  # 需要装饰的函数
    '''函数体代码'''
    return 'XWenXiang'

index = outer(index)
x = index(1, 2)
print(x)

这就是函数装饰器的模板写法,func_name 就是需要装饰的函数名,inner 是返回给新创建的函数名,调用
函数即可。

装饰器语法糖

装饰器有它的语法糖,就是简写版本

语法糖版本

	import time
	
	def outer(func):  # func = index
	    def inner(*args, **kwargs):
	        start_time = time.time()
	        res = func(*args, **kwargs)  # index(1, 2)
	        end_time = time.time()
	        print(end_time - start_time)
	        return res  # index
	    return inner
	
	@outer  # 语法糖简写
	def index(a, b):
	    time.sleep(1)
	    print('from index', a, b)
	    return 'XWenXiang'
	
	print(index(1, 2))

语法糖版本的不同是,首先先创建一个装饰器,然后在定义被装饰函数的时候在前面加上一个 @ ,这个符号
后面跟着你之前创建好的装饰器的名字,其实也就是函数名。这样以后我们就可以直接调用被装饰后的函数了
,比起未使用语法糖时重新赋值更加简便。

装饰器修复技术

我们在前面版本的装饰器可知,后面的变量名 index 是新定义的,相当于 inner ,并不是真正的函数名 index ,所以最终的版本会来解决这个问题,让被装饰的函数看起来更像原函数。

我们可以打印前面版本中的 index ,可以得到下面的内容:
		<function outer.<locals>.inner at 0x000001CDDE250940>
我们可以看到此时 index 显示的却是 inner ,很明显就被看出来了,这时我们可以用修复技术来隐藏
修复版本,也可以称之为 真·终极无敌版本 哈哈

	import time
	from functools import wraps  # 实现修复技术的模块
	
	def outer(func):  # func = index
	    @wraps(func)  # 语法糖
	    def inner(*args, **kwargs):
	        start_time = time.time()
	        res = func(*args, **kwargs)  # index(1, 2)
	        end_time = time.time()
	        print(end_time - start_time)
	        return res  # index
	    return inner
	
	@outer
	def index(a, b):
	    time.sleep(1)
	    print('from index', a, b)
	    return 'XWenXiang'
	
	print(index)

要注意的是,这时虽然显示的是 index 了,但是本质上还是 inner 并没有改变,只是更像了。

多层装饰器

当一个函数被多个装饰器装饰会怎么样?

在多个装饰器时,会按照'自下而上'的顺序传递

代码示例
	
	def outter_a(func_a):
	    print('加载了outter_a')
	    def wrapper_a(*args, **kwargs):
	        print('执行了wrapper_a')
	        res_a = func_a(*args, **kwargs)
	        return res_a
	    return wrapper_a
	
	
	def outter_b(func_b):
	    print('加载了outter_b')
	    def wrapper_b(*args, **kwargs):
	        print('执行了wrapper_b')
	        res_b = func_b(*args, **kwargs)
	        return res_b
	    return wrapper_b
	
	
	def outter_c(func_c):
	    print('加载了outter_c')
	    def wrapper_c(*args, **kwargs):
	        print('执行了wrapper_c')
	        res_c = func_c(*args, **kwargs)
	        return res_c
	    return wrapper_c
	
	
	@outter_a
	@outter_b
	@outter_c
	def index():
	    print('from index')
	
	index()



(1) 我们先创建3个装饰器模板,分别为 outer_a, outer_b, outer_c
(2) 然后定义需要装饰的函数 index() ,定义时在前面添加多个装饰器的语法糖
(3) 传入语法糖的顺序是'由下往上'的,如上代码示例的语法糖顺序此时函数名 index 会被当成实参传到
装饰器 outer_c 中,并得到一个返回值 wrapper_c ,也就是内层函数名。
(4) 接下来返回值 wrapper_c 会被传入上一层装饰器 outer_b 中,也得到了一个返回值 wrapper_b 。
(5) wrapper_b 同样也被传入装饰器 outer_a 中,得到返回值 wrapper_a ,也就是 index 的值为
wrapper_a 。
(6) 调用 index(),也就相当于执行了 wrapper_a(*args, **kwargs)
(7) 由于 fun_a 是由装饰器 outter_b 的返回值作为实参传入的,所以func_a(*args, **kwargs) 此
时等同于 wrapper_b(*args, **kwargs)
(8) 同理,内层函数 wrapper_b(*args, **kwargs) 中的 func_b 是由装饰器 outter_c 的返回值作
为实参传入的,所以func_b(*args, **kwargs) 此时等同于 wrapper_c(*args, **kwargs)
(9) 而装饰器 outter_c 的 func_c 是由 index 作为实参传入,也就是调用了原函数。
(10) 我们可以在调用的时候打印,这样可以看到输出的顺序。
(11) 打印的结果:
				加载了outter_c
				加载了outter_b
				加载了outter_a
				执行了wrapper_a
				执行了wrapper_b
				执行了wrapper_c
				from index
(12) 由此可见装饰器是'由下往上'进行传参调用的

有参装饰器

当我们需要给装饰器里传参数时,需要用到闭包函数

代码示例

		def outer(func):
		    def inner(*args, **kwargs):
		        res = func(*args, **kwargs)
		        return res
		    return inner
		
		
		@outer
		def index(a, b):
		    print(a, b)
		
		index(1,2)

此时我们想传参数的话,如果添加到外层函数 outer()或内层函数 inner()里面显然不可行。
当直接用形参的方式不行的时候,可以考虑闭包的方式来传参数

代码示例

		def outermost(choice):
		    def outer(func):
		        def inner(*args, **kwargs):
		            res = func(*args, **kwargs)
		            if choice == 'A':
		                print('A')
		            elif choice == 'B':
		                print('B')
		            return res
		        return inner
		    return outer
		
		
		@outermost('A')  # 函数名加括号 执行优先级最高
		def index(a, b):
		    print(a, b)
		
		index(1, 2)


(1) 不同的是在外面多加了一层函数,装饰器的参数使用最外层的函数参数。
(2) 设置最外层函数返回值为装饰器的名字 outer
(3) 定义被装饰函数时候,语法糖为最外层的函数加括号,括号里是传给装饰器的实参。
(4) 在遇到左侧是语法糖结构,右侧是函数名加括号结构时,先执行函数 outermost('A') 得到返回值是
outer ,在执行语法糖结构,此时也就是 @outer 了。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值