Python装饰器(Decorator)不过如此,是我想多了

开发 后端
Python装饰器是Python中一个非常有趣的特性,可以利用Python装饰器对一个函数包装再包装,其实从效果上看有一点像AOP中的切面,也就是对函数调用进行拦截,那么通过Python装饰器可以做哪些有趣的事情,以及Python装饰器的原理是什么呢?继续看本文吧!

[[382099]]

 1. 叠加使用Python装饰器

最近有学员问,Python中也有与Java类似的@xxxx语法,这到底是什么意思呢?现在我就来回答这个问题。

Java中的@xxxx语法是注解(Annotation),而Python中的@xxxx语法是装饰器(decorator),尽管在语法上类似,但作用完全不同。Java的注解相当于语法元素(方法、类、接口等)的元数据。而Python的装饰器是对Python函数(方法)的包装,现在我们来举个例子。

  1. @makebold 
  2. @makeitalic 
  3. def say(): 
  4.    return "Hello" 
  5. print(say())) 

这段代码,对函数say使用了2个装饰器:@makebold和@makeitalic,而且是叠加状态。@makeitalic会首先作用于say函数,然后@makebold会作用于@makeitalic装饰器的结果,这两个装饰器分别用......包装say函数返回的字符串,所以这段代码的执行结果如下:

Hello

不过直接执行这段代码肯定会出错的,这是因为这两个装饰器还没定义,下面就看下如何定义这两个装饰器。

2. 定义Python装饰器

装饰器本身就是一个普通的Python函数,只是函数的参数需要是函数类型(通常传入被装饰的函数),定义形式如下:

  1. <b><i>Hello</i></b> 

现在就来定义前面给出的两个装饰器:

  1. from functools import wraps 
  2.  
  3. def makebold(fn): 
  4.     @wraps(fn) 
  5.     def makebold_wrapped(*args, **kwargs): 
  6.         return "<b>" + fn(*args, **kwargs) + "</b>" 
  7.     return makebold_wrapped 
  8.  
  9. def makeitalic(fn): 
  10.     @wraps(fn) 
  11.     def makeitalic_wrapped(*args, **kwargs): 
  12.         return "<i>" + fn(*args, **kwargs) + "</i>" 
  13.     return makeitalic_wrapped 

很明显,makebold和makeitalic是两个普通的Python函数,而且在函数内部分别定义了另外两个函数,而且这两个函数被作为返回值返回。这其中使用了wraps函数,这个函数其实可以不加,不过会有一些副作用。

由于使用@makebold和@makeitalic修饰某个函数时,会将这个被修饰的函数传入makebold函数和makeitalic函数,也就是说,fn参数就是这个被修饰的函数。而在外部调用这个被修饰函数时,实际上是调用了修饰器返回的函数,也就是makebold_wrapped和makeitalic_wrapped,这样就会导致被修饰函数属性的改变,如函数名、函数文档等,现在可以先去掉@wraps,执行下面的代码:

  1. @makeitalic 
  2. @makebold 
  3. def say(): 
  4.    return "Hello" 
  5. print(say.__name__)   # 输出函数名 

会输出如下的内容:

  1. makebold_wrapped 

由于最后使用了@makebold装饰器,所以输出的是makebold函数返回的makebold_wrapped函数的名字。如果加上@wraps,那么就会输出say。

要注意,需要通过装饰器方式调用wraps函数,这样其实就相当于在@makebold外面又包了一层装饰器(wraps)。

3. 理解Python函数

现在我们已经了解了如何自定义Python装饰器,但应该如何理解装饰器呢?到底是什么原理呢?要想理解Python装饰器,首先应该知道Python函数就是对象,看下面的例子:

  1. def shout(word="yes"): 
  2.     return word.capitalize() 
  3. # 输出:Yes 
  4. print(shout()) 
  5. # 将shout函数赋给另一个变量,这里并没有使用圆括号, 
  6. # 所以不是调用函数,而是将函数赋给另一个变量,也就是为函数起一个别名 
  7. scream = shout 
  8.  
  9. # 可以用scream调用shout函数 
  10. # 输出:Yes 
  11. print(scream()) 
  12.  
  13. # 目前,同一个函数,有两个引用:scream和shout,可以使用del删除一个引用 
  14. del shout 
  15. try: 
  16.     # 该引用删除后,就不能通过该引用调用函数了 
  17.     print(shout()) 
  18. except NameError as e: 
  19.     print(e) 
  20.  
  21. # 仍然可以通过另外一个引用调用函数 
  22. # 输出:Yes 
  23. print(scream()) 

这段代码演示了把函数作为对象使用。如果加一对圆括号,就是调用函数,如果不加一对圆括号,函数就是对象,可以赋给另一个变量,也可以作为函数参数值传入函数。

由于Python函数本身是对象,所以可以在任何地方定义,包括函数内容,这就是Python内建函数,代码如下:

  1. def talk(): 
  2.     # 内嵌函数 
  3.     def whisper(word="YES"): 
  4.         return word.lower()+"..." 
  5.  
  6.     # 调用内嵌函数 
  7.     print(whisper()) 
  8.  
  9. # 调用talk,whisper函数在talk内部被调用 
  10. # 输出:yes... 
  11. talk() 
  12.  
  13. try: 
  14.     # 但whisper函数在talk函数外部并不可见,所以调用会哦抛出异常 
  15.     print(whisper()) 
  16. except NameError as e: 
  17.     print(e) 

现在来总结下,Python函数的特性如下:

(1)可以将函数本身赋给一个变量,或作为参数值传入函数(方法);

(2)可以在一个函数(方法)内部定义;

有了这两个特性,就意味着函数可以被另一个函数返回,看下面的代码:

  1. def getTalk(kind="shout"): 
  2.  
  3.     #  定义第1个内嵌函数 
  4.     def shout(word="yes"): 
  5.         return word.capitalize()+"!" 
  6.     # 定义第2个内嵌函数 
  7.     def whisper(word="yes") : 
  8.         return word.lower()+"..." 
  9.  
  10.     # 根据参数值返回特定的函数 
  11.     if kind == "shout"
  12.         # 这里没有使用一对圆括号,所以不是调用函数,而是返回函数本身 
  13.         return shout 
  14.     else
  15.         return whisper 
  16.  
  17.  
  18. # talk是函数本身,并没有被调用 
  19. talk = getTalk() 
  20.  
  21. # 输出函数本身 
  22. # 输出:<function getTalk.<locals>.shout at 0x7f93a00475e0> 
  23. print(talk) 
  24.  
  25. # 调用talk函数(其实是shout函数) 
  26. print(talk()) 
  27. #outputs : Yes! 
  28.  
  29. # 调用whisper函数 
  30. print(getTalk("whisper")()) 

在这段代码中,getTalk函数根据kind参数的值返回不同的内嵌函数,所以getTalk函数的返回值是函数本身,或称为函数对象,如果要调用函数,需要使用一对圆括号,如getTalk()()。

根据这一特性,我们还可以做更多事,例如,在调用一个函数之前自动完成其他工作,看下面的代码:

  1. def doSomethingBefore(func): 
  2.     print("I do something before then I call the function you gave me"
  3.     print(func()) 
  4.  
  5. doSomethingBefore(talk) 

其实这段代码用doSomethingBefore函数包装了talk,这样可以通过doSomethingBefore函数调用talk函数,并在调用talk函数之前输出一行文本。

4. Python装饰器的原理

理解了Python函数,再理解Python装饰器就容易得多了。废话少说,先看下面的代码:

  1. # 装饰器函数,参数是另一个函数(被装饰的函数) 
  2. def my_shiny_new_decorator(a_function_to_decorate): 
  3.     # 装饰器的内嵌函数,用来包装被修饰的函数 
  4.     def the_wrapper_around_the_original_function(): 
  5.         # 在调用被修饰函数之前输出一行文本 
  6.         print("Before the function runs"
  7.  
  8.         # 调用被装饰函数 
  9.         a_function_to_decorate() 
  10.  
  11.         # 在调用被修饰函数之后输出一行文本 
  12.         print("After the function runs"
  13.  
  14.     # 返回包装函数 
  15.     return the_wrapper_around_the_original_function 
  16.  
  17. # 这个函数将被my_shiny_new_decorator函数修饰 
  18. def a_stand_alone_function(): 
  19.     print("I am a stand alone function, don't you dare modify me"
  20.  
  21. # 调用函数 
  22. a_stand_alone_function() 
  23.  
  24. # 修饰a_stand_alone_function函数 
  25. a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function) 
  26. a_stand_alone_function_decorated() 

执行这段代码,会输出如下内容:

  1. I am a stand alone function, don't you dare modify me 
  2. Before the function runs 
  3. I am a stand alone function, don't you dare modify me 
  4. After the function runs 

在这段代码中,通过my_shiny_new_decorator函数修饰了a_stand_alone_function函数,并在调用a_stand_alone_function函数前后各输出了一行文本。其实这就是Python装饰器的作用:包装函数。只是这里并没有使用装饰器的语法,而是用了最朴素的方式直接调用了装饰器函数来修饰a_stand_alone_function函数。

如果用装饰器来修饰a_stand_alone_function函数,那么可以用下面的代码。

  1. @my_shiny_new_decorator 
  2. def a_stand_alone_function(): 
  3.     print("I am a stand alone function, don't you dare modify me"

这时再调用a_stand_alone_function函数,就会自动使用my_shiny_new_decorator函数对a_stand_alone_function函数进行包装,也就是说,@my_shiny_new_decorator是my_shiny_new_decorator(a_stand_alone_function)的简写形式。

本文转载自微信公众号「极客起源」,可以通过以下二维码关注。转载本文请联系极客起源公众号。

 

责任编辑:武晓燕 来源: 极客起源
相关推荐

2021-05-11 09:27:54

装饰器模式代码开发

2018-12-29 16:40:29

c语言编程语言指针

2020-05-21 08:24:17

阿里SQL查询

2020-08-04 11:35:38

Vue前端装饰器

2017-07-27 20:50:55

PythonDecorator装饰器

2023-02-07 07:47:52

Python装饰器函数

2010-02-01 17:50:32

Python装饰器

2021-06-17 09:32:17

前端TypeScript 技术热点

2022-09-19 23:04:08

Python装饰器语言

2016-11-01 09:24:38

Python装饰器

2023-12-11 15:51:00

Python装饰器代码

2021-06-01 07:19:58

Python函数装饰器

2010-12-29 11:39:29

老板

2021-04-11 08:21:20

Python@property装饰器

2023-12-13 13:28:16

装饰器模式Python设计模式

2022-09-21 09:04:07

Python装饰器

2018-07-23 13:16:24

编程语言Python装饰器

2018-05-21 09:30:04

操作系统Linux资源

2012-07-06 09:27:29

jQuery2.0

2022-12-31 19:20:15

JavaScriptstage 3装饰器
点赞
收藏

51CTO技术栈公众号