Python 装饰器

1. 装饰器介绍

2. 装饰器示例 

3. 两个装饰器

4. 被装饰的函数带参数

5. 被装饰的函数带 return

6. 带参数的装饰器

 

 

1. 装饰器介绍

装饰器(decorator)是程序开发中经常会用到的一个功能,用好了装饰器,开发效率如虎添翼,所以这也是 Python 面试中必问的问题。但对于好多初次接触这个知识的人来讲,这个功能有点绕,自学时直接绕过去了。装饰器是程序开发的基础知识,若不懂装饰器,则不能自称会 Python 了。

装饰器本质上是一个可调用对象(callable object),它可以让其它函数内部在不需要做任何代码改动的前提下增加额外的功能。装饰器的返回值也是一个函数对象。有了装饰器,可以抽离出大量的与其它函数功能本身无关的相同代码,提高复用性。

装饰器常用于有切面需求的场景:

  • 引入日志
  • 函数执行时间统计
  • 执行函数前预备处理
  • 执行函数后清理功能
  • 权限校验等场景
  • 缓存

 

2. 装饰器示例

首先理解函数引用

 1 >>> def foo():
 2 ...     print("foo")
 3 ...
 4 >>> foo  # foo这个名称所指向的函数体内存地址
 5 <function foo at 0x0313DC88>
 6 >>> foo()  # 执行函数
 7 foo
 8 >>> foo = lambda x: x+1
 9 >>> foo()  # 此时调用的是匿名函数,因为foo这个名称已经重新指向了另一个匿名函数
10 Traceback (most recent call last):
11   File "<stdin>", line 1, in <module>
12 TypeError: <lambda>() missing 1 required positional argument: 'x'

需求

有一个通用的验证功能(函数),需要其它每个函数在执行时都需要先进行验证功能的调用。

普通实现方式

 1 def check_login():
 2     # 验证1
 3     # 验证2
 4     # 验证3
 5     pass
 6 
 7 def f1():
 8     check_login()
 9     print('f1')
10 
11 def f2():
12     check_login()
13     print('f2')
14 
15 def f3():
16     check_login()
17     print('f3')
18 
19 def f4():
20     check_login()
21     print('f4')

装饰器实现方式

写代码要遵循开放封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程。简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:

  • 封闭:已实现的功能代码块
  • 开放:扩展开发

如果将开放封闭原则应用在上述需求中,那么就不允许在函数 f1 、f2、f3 等的内部进行修改代码。接下来看下使用装饰器的实现方式:

 1 # 通用功能使用闭包实现
 2 def check(func):
 3     
 4     def check_inner():
 5         print("验证通过")
 6         func()  # 保存的是原f1或f2的函数对象
 7     
 8     return check_inner
 9     
10    
11 @check  # 等价于f1 = check(f1),f1先作为参数传递给check()后,f1再接收并指向check()返回的check_inner
12 def f1():
13     print("f1")
14 
15 @check  # 等价于f2 = check(f2)
16 def f2():
17     print("f2")   
18 
19 f1()
20 f2()

"@函数名"是 python 的一种语法糖。对于上述代码,仅仅对通用功能的代码进行修改,就可以实现在其他人调用函数 f1、f2 时无需修改函数内部代码就可以进行“验证”操作。

装饰器的开始执行时间

 1  # 通用功能使用闭包实现
 2 def check(func):
 3     
 4     print("---decoration---")
 5     
 6     def check_inner():
 7         print("验证通过")
 8         func()
 9     
10     return check_inner
11     
12     
13 @check  # python解释器在执行到该行时,已在调用内部函数外的代码
14 def f1():
15     print("f1")

此时的执行结果:

E:\python_pra>python test.py
---decoration---

 

3. 两个装饰器

注意执行顺序:

 1 # 定义函数,完成前端包裹数据的效果
 2 def makeBold(fn):
 3     def wrapped():
 4         return "<b>" + fn() + "</b>"
 5     return wrapped    
 6     
 7     
 8 def makeItalic(fn):
 9     def wrapped():
10         return "<i>" + fn() + "</i>"
11     return wrapped  
12     
13 
14 @makeBold
15 def test1():
16     return "hello world-1"
17 
18 @makeItalic    
19 def test2():
20     return "hello world-2"
21     
22 @makeBold  # test3 = makeBold(test3)。test3最终指向makeBold的wrapped
23 @makeItalic  # test3 = makeItalic(test3)。调用结果返回给makeBold()
24 def test3():  
25     return "hello world-3"
26     
27     
28 print(test1())  # <b>hello world-1</b>
29 print(test2())  # <i>hello world-2</i>
30 print(test3())  # <b><i>hello world-3</i></b>。即先调用makeItalic(),再调用makeBold()

 

4. 被装饰的函数带参数

 1 from time import ctime, sleep
 2 
 3 def timefun(func):
 4     def wrappedfunc(a, b):  # 如果没有参数,会导致16、18行调用失败
 5         print("%s called at %s" % (func.__name__, ctime()))
 6         print(a, b)
 7         func(a, b)  # 如果没有参数,会导致12行调用失败
 8     return wrappedfunc
 9     
10 
11 @timefun
12 def foo(a, b):
13     print(a + b)
14     
15     
16 foo(3, 5)
17 sleep(2)
18 foo(2, 4)

执行结果:

foo called at Tue Feb 25 21:59:37 2020
3 5
8
foo called at Tue Feb 25 21:59:39 2020
2 4
6

 

5. 被装饰的函数带 return

一般情况下,为了让装饰器更通用,可以有 return 。

 1 from time import ctime, sleep
 2 
 3 def timefun(func):
 4     def wrappedfunc():
 5         print("%s called at %s" % (func.__name__, ctime()))
 6         return func()  # 接收原getInfo函数的返回值,并返回给result
 7     return wrappedfunc
 8     
 9 
10 @timefun
11 def getInfo():
12     return "--get the infomation--"
13     
14     
15 result = getInfo()
16 print(result)

执行结果:

getInfo called at Tue Feb 25 22:52:53 2020
--get the infomation--

通用的装饰器

可装饰无参数、有参数、带 return 的函数。

 1 from time import ctime, sleep
 2 
 3 def timefunc(func):
 4     def wrappedfunc(*args, **kwargs):
 5         print("%s called at %s" % (func.__name__, ctime()))
 6         return func(*args, **kwargs)
 7     return wrappedfunc
 8     
 9 
10 @timefunc
11 def getRetun():
12     return "--get the return--"
13 
14 @timefunc
15 def getPrint():
16     print("---get print ---")    
17         
18 @timefunc
19 def getArgs(*args, **kwargs):
20     print(args)
21     print(kwargs)
22 
23 @timefunc
24 def getArg(a, b, c):
25     print(a+b+c)
26 
27 
28 print(getRetun())
29 print(getPrint())
30 print(getArgs(1, "a", a=1, b=2))
31 print(getArg(1, 2, 3))

执行结果:

getRetun called at Tue Feb 25 23:29:23 2020
--get the return--
getPrint called at Tue Feb 25 23:29:23 2020
---get print ---
None
getArgs called at Tue Feb 25 23:29:23 2020
(1, 'a')
{'a': 1, 'b': 2}
None
getArg called at Tue Feb 25 23:29:23 2020
6
None

 

6. 带参数的装饰器

 1 def func_arg(check_num):
 2     def func(funcname):
 3         def func_in():
 4             print("---记录日志---")
 5             # 带有参数的装饰器,能够起到在运行时,有不同的功能
 6             # if check_num ... 
 7             print("check time: {}".format(check_num))
 8             funcname()
 9         return func_in
10     return func
11     
12 # 1. 先执行func_arg("check1")函数,然后返回的是func这个函数的引用
13 # 2. @func
14 # 3. 使用@func对test进行修饰    
15 @func_arg("check1")
16 def test1():
17     print("--test1--")
18     
19 @func_arg("check2")
20 def test2():
21     print("--test2--")
22     
23 test1()
24 test2()

执行结果:

---记录日志---
check time: check1
--test1--
---记录日志---
check time: check2
--test2--

 

posted @ 2020-02-26 17:00  Juno3550  阅读(150)  评论(0编辑  收藏  举报