Python @函数装饰器及用法(超级详细)

使用 @ 符号引用已有的函数(比如 @staticmethod、@classmethod)后,可用于修饰其他函数,装饰被修饰的函数。那么我们是否可以开发自定义的函数装饰器呢?

答案是肯定的。当程序使用“@函数”(比如函数 A)装饰另一个函数(比如函数 B)时,实际上完成如下两步:

  1. 将被修饰的函数(函数 B)作为参数传给 @ 符号引用的函数(函数 A)。
  2. 将函数 B 替换(装饰)成第 1 步的返回值。


从上面介绍不难看出,被“@函数”修饰的函数不再是原来的函数,而是被替换成一个新的东西。

为了让大家厘清函数装饰器的作用,下面看一个非常简单的示例:

 
  1. def funA(fn):
  2. print('A')
  3. fn() # 执行传入的fn参数
  4. return 'fkit'
  5. '''
  6. 下面装饰效果相当于:funA(funB),
  7. funB将会替换(装饰)成该语句的返回值;
  8. 由于funA()函数返回fkit,因此funB就是fkit
  9. '''
  10. @funA
  11. def funB():
  12. print('B')
  13. print(funB) # fkit

上面程序使用 @funA 修饰 funB,这意味着程序要完成两步操作:

  • 将 funB 作为 funA() 的参数,也就是上面代码中 @funA 相当于执行 funA(funB)。
  • 将 funB 替换成上一步执行的结果,funA() 执行完成后返回 fkit,因此 funB 就不再是函数,而是被替换成一个字符串。


运行上面程序,可以看到如下输出结果:

A
B
Fkit

通过这个例子,相信读者对函数装饰器的执行关系己经有了一个较为清晰的认识,但读者可能会产生另一个疑问:这个函数装饰器导致被修饰的函数变成了字符串,那么函数装饰器有什么用?

别忘记了,被修饰的函数总是被替换成 @ 符号所引用的函数的返回值,因此被修饰的函数会变成什么,完全由于 @ 符号所引用的函数的返回值决定,换句话说,如果 @ 符号所引用的函数的返回值是函数,那么被修饰的函数在替换之后还是函数。

下面程序示范了更复杂的函数装饰器:

 
  1. def foo(fn):
  2. # 定义一个嵌套函数
  3. def bar(*args):
  4. print("===1===", args)
  5. n = args[0]
  6. print("===2===", n * (n - 1))
  7. # 查看传给foo函数的fn函数
  8. print(fn.__name__)
  9. fn(n * (n - 1))
  10. print("*" * 15)
  11. return fn(n * (n - 1))
  12. return bar
  13. '''
  14. 下面装饰效果相当于:foo(my_test),
  15. my_test将会替换(装饰)成该语句的返回值;
  16. 由于foo()函数返回bar函数,因此funB就是bar
  17. '''
  18. @foo
  19. def my_test(a):
  20. print("==my_test函数==", a)
  21. # 打印my_test函数,将看到实际上是bar函数
  22. print(my_test) # <function foo.<locals>.bar at 0x00000000021FABF8>
  23. # 下面代码看上去是调用my_test(),其实是调用bar()函数
  24. my_test(10)
  25. my_test(6, 5)

上面程序定义了一个装饰器函数 foo,该函数执行完成后并不是返回普通值,而是返回 bar 函数(这是关键),这意味着被该 @foo 修饰的函数最终都会被替换成 bar 函数。

上面程序使用 @foo 修饰 my_test() 函数,因此程序同样会执行 foo(my_test),并将 my_test 替换成 foo() 函数的返回值:bar 函数。所以,上面程序第二行粗体字代码在打印 my_test 函数时,实际上输出的是 bar 函数,这说明 my_test 已经被替换成 bar 函数。接下来程序两次调用 my_test() 函数,实际上就是调用 bar() 函数。

运行上面程序,可以看到如下输出结果:

<function foo.<locals>.bar at 0x000001C2A5953510>
===1=== (10,)
===2=== 90
my_test
==my_test函数== 90
***************
==my_test函数== 90
===1=== (6, 5)
===2=== 30
my_test
==my_test函数== 30
***************
==my_test函数== 30

通过 @ 符号来修饰函数是 Python 的一个非常实用的功能,它既可以在被修饰函数的前面添加一些额外的处理逻辑(比如权限检查),也可以在被修饰函数的后面添加一些额外的处理逻辑(比如记录日志),还可以在目标方法抛出异常时进行一些修复操作……这种改变不需要修改被修饰函数的代码,只要增加一个修饰即可。

上面介绍的这种在被修饰函数之前、之后、抛出异常后增加某种处理逻辑的方式,就是其他编程语言中的 AOP(Aspect Orient Progiuning,面向切面编程)。

下面例子示范了如何通过函数装饰器为函数添加权限检查的功能。程序代码如下:

 
  1. def auth(fn):
  2. def auth_fn(*args):
  3. # 用一条语句模拟执行权限检查
  4. print("----模拟执行权限检查----")
  5. # 回调要装饰的目标函数
  6. fn(*args)
  7. return auth_fn
  8. @auth
  9. def test(a, b):
  10. print("执行test函数,参数a: %s, 参数b: %s" % (a, b))
  11. # 调用test()函数,其实是调用装饰后返回的auth_fn函数
  12. test(20, 15)

上面程序使用 @auth 修饰了 test() 函数,这会使得 test() 函数被替换成 auth() 函数所返回的 auth_fn 函数,而 auth_fn 函数的执行流程是:

  1. 先执行权限检查;
  2. 回调被修饰的目标函数。简单来说,auth_fn 函数就为被修饰函数添加了一个权限检查的功能。


运行该程序,可以看到如下输出结果:

----模拟执行权限检查----
执行test函数,参数a: 20, 参数b: 15

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值