Python装饰器

1. 什么是装饰器?python

  顾名思义,装饰器就是在方法上方标一个带有@符号的方法名,以此来对被装饰的方法进行点缀改造。shell

  当你明白什么是装饰器以后,天然会以为这个名字取得恰如其分,但做为初学者来讲多少仍是会有些迷茫。下面用代码来讲明怎么理解装饰器。函数

 1 #脚本1
 2 def target():
 3     print('this is target')
 4 
 5 def decorator(func):
 6     func()
 7     print('this is decorator')
 8 
 9 decorator(target)
10 -------------------------------------------
11 
12 运行结果为:
13 
14 this is target
15 this is decorator

  Python容许将方法看成参数传递,所以以上脚本就是将target方法做为参数传入decorator方法中,这其实也是装饰器的工做原理,以上代码等同于:学习

 1 #脚本2
 2 def decorator(func):
 3     func()
 4     print('this is decorator')
 5 
 6 @decorator
 7 def target():
 8     print('this is target')
 9 
10 target
11 -------------------------------------------
12 运行结果:
13 
14 this is target
15 this is decorator

  所以能够看出,所谓的装饰器就是利用了Python的方法可做参数传递的特性,将方法target做为参数传递到方法decorator中。ui

1 @decorator
2 def target():
3     ...

  这种在一个方法的上方加一个@符号的写法,就是表示位于下方的方法将被做为参数传递到位于@后面的decorator方法中。使用@符号只是让脚本1中的代码换了一个写法,更加好看,固然也会更加灵活与实用,后面会讲到这点。但它们的本质实际上是同样的,这也就是装饰器的工做原理。this

2. 装饰器的原理spa

  若是你仔细看的话,会在脚本2中发现一个问题,那就是脚本2中最后一行的target只是一个方法名字,它不是正确的方法调用,正确写法应该加上左右括号的target(),以下:.net

 1 #脚本3
 2 
 3 def decorator(func):
 4     func()
 5     print('this is decorator')
 6 
 7 @decorator
 8 def target():
 9     print('this is target')
10 
11 target()
12 --------------------------------------------
13 运行结果:
14 
15 this is target
16 this is decorator
17 Traceback (most recent call last):
18   File "C:/Users/Me/Desktop/ff.py", line 34, in <module>
19     target()
20 TypeError: 'NoneType' object is not callable

  正如你所看到的,若是按照正确的写法,运行结果你会看到应该出现的两行打印文字"this is target"和"this is decorator",还会出现错误提示,ff.py是我为写这篇心得临时编写的一个py脚本名字,提示说'NoneType'对象不可调用。这是怎么回事?好吧,我如今必须告诉你,其实脚本2和脚本3中并非一个使用装饰器的正确写法,不是使用错误,而是做为装饰器的decorator方法写的并不友好,是的,我不认为它是错误的写法,只是不友好。但只要你明白其中的道理,使用恰当的手段也是能够运行正常的,这就是为何脚本2看似写错了调用方法却得出了正确的结果。固然学习仍是得规规矩矩,后面我会具体说正确的装饰器怎么书写,在这里我先解释了一下脚本2和脚本3的运行原理,了解它们的运行原理和错误缘由,其实就是了解装饰器的原理。设计

  脚本2和脚本3的区别在于target和target(),也就是说真正的差异在于()这个括号。当()被附加在方法或者类后面时,表示调用,或者称为运行及实例化,不管称呼怎样,本质意义没有不一样,都是调用给出的对象,当对象不具有调用性的时候,就会报错:'某个类型' object is not callable。当一个方法被调用后,即target(),是否能被再次执行,取决于它是否会return一个对象,而且该对象能够被调用。也许你会有点迷糊,对比一下代码会比较容易理解我想表达的意思:rest

 1 >>>def returnme():
 2 >>>    print('this is returnme')
 3  
 4 >>>def target():
 5 >>>    print('this is target')
 6   
 7 >>>target
 8 <function target at 0x00000000030A40D0>
 9   
10 >>>target()
11 target
12 <function returnme at 0x00000000030A4268>
13  
14 >>>target()()
15 target
16 returnme
17 
18 >>>returnme()()
19 returnme
20 Traceback (most recent call last):
21   File "<pyshell#15>", line 1, in <module>
22     returnme()()
23 TypeError: 'NoneType' object is not callable

  如上所示,当直接在脚本中输入target,它只是告诉编译器(我想是编译器吧,由于我也不是很懂所谓编译器的部分),总之就是告诉那个不知道在哪一个角落控制着全部python代码运行的“大脑”,在

  0x00000000030A40D0位置(这个位置应该是指内存位置)存有一个function(方法)叫target;在target后面加上(),表示调用该方法,即输入target(),“大脑”便按照target方法所写的代码逐条执行,因而打印出了target字符串,而且“大脑”明白在0x00000000030A4268位置有一个叫returnme的方法;由于target对象调用后是会返回一个returnme方法,而且方法是能够被调用的,所以你能够直接这样书写target()(),“大脑”会逐条执行target中的代码,而后return一个returnme,由于多加了一个(),表示要对返回的returnme进行调用,因而再次逐条执行returnme中的代码,最后便能看到1五、16的打印结果;而returnme方法是没有返回任何可调用的对象,所以当输入returnme()()时,“大脑”会报错。

  下面咱们能够来解释一下脚本2和脚本3的运行详情,以前说过,装饰器的工做原理就是脚本1代码所演示的那样。

 1 @decorator
 2 def target():
 3     ...
 4 
 5 等同于
 6 
 7 def decorator(target)():
 8     ...
 9 
10 注:python语法中以上写法是非法的,以上只是为了便于理解。

  当你调用被装饰方法target时,其实首先被执行的是做为装饰器的decorator函数,而后“大脑”会把target方法做为参数传进去,因而:

 1 #脚本2
 2 def decorator(func):
 3     func()
 4     print('this is decorator')
 5   
 6 @decorator
 7 def target():
 8     print('this is target')
 9   
10 target
11 -------------------------------------------
12 
13 实际运行状况:
14 首先调用decorator方法:decorator()
15 由于decorator方法含1个参数,所以将target传入:decorator(target)
16 运行代码“func()”,根据传入的参数,实际执行target(),结果打印出:this is target
17 运行代码"print('this is decorator')",结果打印出:this is decorator

  对比脚本3的运行状况:

 1 #脚本3
 2 def decorator(func):
 3     func()
 4     print('this is decorator')
 5   
 6 @decorator
 7 def target():
 8     print('this is target')
 9   
10 target()
11 -------------------------------------------
12 
13 实际运行状况:
14 首先调用decorator方法:decorator()
15 由于decorator方法含1个参数,所以将target传入:decorator(target)
16 运行代码“func()”,根据传入的参数,实际执行target(),结果打印出:this is target
17 运行代码"print('this is decorator')",结果打印出:this is decorator
18 
19 以上与脚本2中运行状况彻底相同,接下来即是执行脚本2中target没有的(),也就是执行调用命令。
20 因为decorator(target)没有返回一个能够被调用的对象,所以“大脑”提示错误:'NoneType' object is not callable

  若是你还不是很清楚,请看下面的等价关系:

 1 @decorator
 2 def target():
 3     ...
 4 
 5 等同于
 6 def decorator(target)():
 7     ...
 8 
 9 所以:
10 target == decorator(target)
11 target() == decorator(target)()
12 
13 因此:
14 假设有一个变量var=target,在将target赋值给var时,实际上是将decorator(target)的调用结果赋值给var,由于var不具有调用性(not callable),所以执行var()时,编译器会报错它是个NoneType对象,不能调用。

  综上所述,你大概已经可以明白所谓的装饰器是怎么一回事,它是怎么工做的。但脚本2和脚本3中的写法会带来一些困惑,这个困惑就是经过咱们编写的decorator装饰器对target进行装饰后,将target变成了一个永远不能被调用的方法,或者说变成了一个调用就报错的方法。这跟咱们的使用习惯以及对方法的认识是很不协调的,毕竟咱们仍是习惯一个方法天生注定能够被调用这种见解。因此为了知足咱们对方法的定义,咱们最好将做为装饰器的方法写成一个能够返回具备被调用能力的对象的方法。

 1 #脚本4
 2 def whatever():
 3     print('this is whatever')
 4 
 5 def decorator(func):
 6     func()
 7     print('this is decorator')
 8     return whatever  #1
 9 
10 @decorator
11 def target():
12     print('this is target')
13 
14 ------------------------------
15 输入:target
16 结果:
17 this is target
18 this is decorator
19 
20 输入:target()
21 结果:
22 this is target
23 this is decorator
24 this is whatever

  在#1的位置,你能够return任何能够被调用的方法或类,甚至你能够直接写成:

 1 def whatever():
 2     print('this is whatever')
 3 
 4 def decorator(func):
 5     return whatever
 6 
 7 @decorator
 8 def target():
 9     print('this is target')
10 
11 ------------------------------
12 输入:target
13 结果:告诉编译器在内存某个位置有一个叫whatever的方法
14 
15 输入:target()
16 结果:this is whatever

  以上装饰器的做用就是将target方法,完彻底全变成了whatever方法。但这只能完美解释装饰器的功效,在实际当中则毫无心义,为何要辛辛苦苦写了一大堆代码以后,最后的结果倒是完完整整地去调用另外一个早已存在的方法?若是要调用whatever方法,我为何不在个人代码中直接写whatever呢?

  装饰器,顾名思义,它就是要对被装饰的对象进行一些修改,经过再包装来达到不同的效果,尽管它能够将被装饰对象变得面目全非或者直接变成另外一个对象,但这不是它被发明出来的主要意义,相反它被发明出来是帮助咱们更高效更简洁的编写代码,对其余方法或类进行装饰,而非摧毁它们。例如对接收数据的检校,咱们只要写好一个校验方法,即可以在其余许多方法前做为装饰器使用。

3. 常规的装饰器

  从广义上来讲,装饰器就是脚本1中,利用python能够将方法传参的特性,用一个方法去改变另外一个方法,只要改变成功,均可以认为是合格的装饰器。但这是理论上的合格,毕竟装饰器是一种语法糖,应该是为咱们带来便利而不是无用功,因此:

 1 1. 将方法装饰成不能被调用,很差:
 2 def decorator(func):
 3     func()
 4     print('this is decorator')
 5 
 6 2. 将原方法完全消灭,直接变成另外一个方法,很差:
 7 def decorator(func):
 8     return whatever
 9 
10 3. 保留原方法的同时再加上别的功能,很差:
11 def decorator(func):
12     func()
13     print('this is decorator')
14     return whatever

  在以上3种写法中,前两种明显很差,简直就是将装饰器变成了恶魔。而第3种写法,虽然看起来从新包装了被修饰方法,但却在方法调用前擅自执行了一些命令,即当你输入target,而非target()时:

 1 #脚本4
 2 def whatever():
 3     print('this is whatever')
 4 
 5 def decorator(func):
 6     func()
 7     print('this is decorator')
 8     return whatever  #1
 9 
10 @decorator
11 def target():
12     print('this is target')
13 
14 ------------------------------
15 输入:target
16 结果:
17 this is target
18 this is decorator

  你还没有执行target(),编译器却已经打印了两行字符串。这并非咱们想要的,当咱们在代码中写下target时,咱们是不但愿编译器当即执行什么命令,咱们是但愿编译器在碰到target()时才执行运算。并且若是咱们并不但愿返回whatever,咱们只想要经过装饰器,使得target方法除了打印本身的"this is target",再多打印一行"this is decorator”,全部代码只含有target和decorator两个方法,无其余方法介入,应该怎么办?

  当你这样问的时候,其实就是已经开始了解装饰器存在的意义了。如下即是为解决这些问题的装饰器的常规写法:

 1 #脚本5
 2 
 3 def decorator(func):
 4     def restructure():    
 5         func()
 6         print('this is decorator')
 7     return restructure
 8 
 9 @decorator
10 def target():
11     print('this is target')

  是的,从最外层讲,以上代码其实只有两个方法,decorator和target——装饰和被装饰方法。但在decorator内部内嵌了一个方法restructure,这个内嵌的方法才是真正改造target的东西,而decorator其实只是负责将target传入。这里的restructure,至关于在脚本4中,被写在decorator外面的whatever角色,用装饰器的常规写法也能够写出脚本4的效果,以下:

 1 def decorator(func):   
 2     func()
 3     print('this is decorator')
 4 
 5     def whatever():
 6         print('this is whatever')     
 7     return restructure
 8 
 9 @decorator
10 def target():
11     print('this is target')

  对比以上的写法你会发现,python的方法传参和类的继承性质很类似,在decorator以内,whatever以外,你能够写入任何代码,当执行target时,就开始初始化decorator,也就是执行decorator内部能够执行的代码;当执行target()时,会对初始化以后的decorator开始调用,所以这就要求decorator完成初始化以后必须返回一个具有调用性的对象。因此,除非你想要target方法初始化以前(其实是对decorator进行初始化)就执行一些代码,不然不要在decorator和whatever中间插入代码。

  正常状况下,当decorator完成初始化,应该return一个可调用对象,也就是脚本5中的restructure方法,这个方法就是替代target的克隆人,在restructure中你能够对target进行重写,或其余代码来包装target。所以你只是想初始化target的话(实际就是对restructure初始化),就应将你要初始化的代码写入到restructure内部去。另外你也能够在decorator中内嵌多个方法,或多层方法,例如:

 1 #脚本6
 2 
 3 def decorator(func):
 4     def restructure():    
 5         func()
 6         print('this is decorator')
 7 
 8     def whatever():
 9         func()
10         print('this is whatever')
11 
12     return restructure
13 
14 @decorator
15 def target():
16     print('this is target')

  被decorator装饰的target最后会多打印一行'this is decorator'仍是'this is whatever’,取决于decorator方法return的是它内部的哪个方法(restructure或whatever),所以以上代码等价于如下写法:

执行target()

等同于

首先初始化decorator(target),结果返回一个restructure,即:target == decorator(target) == restructure。

而后调用,即:target() == decorator(target)() == restructure()

与类同样,当target被传入decorator以后,做为decorator内嵌方法是能够调用(继承)target方法的,这就是为何restructure不用接受传参就能够改造target的缘由。

注:在python中,以上多个()()的写法是非法的,这样写只是为了便于理解。

  装饰器这个概念原本也能够设计成多个()这样的形式,但这样就破坏了python的基本写法,并且很差看,尤为当有多重装饰的时候,所以使用@置于方法前的设计是更加优雅和清晰的。

  我之因此使用多个()这样并不被python支持的写法来阐述我对python的装饰器的理解,是由于我相信经过这样被python放弃的语法也能更好地帮助你理解python的继承、传参以及装饰器,尤为是带有参数的装饰器。

4. 装饰带有参数的方法

  首先请看如下代码:

 1 def target(x):
 2     print('this is target %s'%x)
 3 
 4 def decorator(func,x):
 5     func(x)
 6     print('this is decorator %s'%x)
 7 
 8 decorator(target,'!')
 9 
10 等同于:
11 
12 def decorator(func):
13     def restructure(x):
14         func(x)
15         print('this is decorator %s'%x)
16     return restructure
17 
18 @decorator
19 def target(x):
20     print('this is target %s'%x)
21 
22 target('!')

  target(x)中的参数x是如何传入,何时传入装饰器的?首先尝试如下代码:

 1 def decorator(func):
 2     print(x)  #增长一行代码
 3     def restructure(x):
 4         func(x)
 5         print('this is decorator %s'%x)
 6     return restructure
 7 
 8 @decorator
 9 def target(x):
10     print('this is target %s'%x)
11 
12 target('!')

  此时编译器会报错参数x没有被定义,也就是说在初始化decorator的时候,只有target方法被传入,其参数x='!'并无传入。

  如今让咱们回顾一下以前说的装饰器工做原理,以下:

1 target == decorator(target) == restructure。
2 
3 target() == decorator(target)() == restructure()
4 
5 同理:
6 
7 target(x) == decorator(target)(x) == restructure(x)

  因此,你能够很清楚地了解到,做为target的参数,其实不是传给decorator,而是传给初始化完decorator以后return的restructure。所以,若是装饰器写成以下代码: 

1 def decorator(func):
2     def restructure():    #不带参数的方法
3         func(x)
4         print('this is decorator %s'%x)
5     return restructure

  此时你输入target('!'),编译器会告诉你restructure没有参数,但被传入了1个参数,这个被传入的参数就是x='!'。

  因此你如今明白了被装饰的方法target(x),方法target和参数x是如何被传入的,因此你必须保证初始化decorator以后返回的对象restructure方法形参与被装饰的target方法形参相匹配,即:

1 若是定义为:def target(x)
2 则装饰器中:def restructure(x)
3 
4 若是定义为:def target(x,y)
5 则装饰器中:def restructure(x,y)

  你也许会发现若是想装饰器同时装饰target(x)和newtarget(x,y),以上写法就没法知足要求了。所以为了让装饰器能够适用于更多的对象,咱们最好让装饰器写成以下形式:

 1 def decorator(func):
 2     def restructure(*x):
 3         func(*x)
 4         print('this is decorator')
 5     return restructure
 6 
 7 @decorator
 8 def target(x):
 9     print('this is target %s'%x)
10 
11 @decorator
12 def newtarget(x,y):
13     print('this is target %s%s'%(x,y))
14 
15 target('!')
16 newtarget('!','?')

  利用python的带星号参数语法(*arg),你即可以传入任意数量的参数,你也能够设置带双星号的形参(**arg),即可以传入字典形式的参数,单星形参和双星形参能够同时使用,如:def restructure(*arg, **arg)。

5. 带有参数的装饰器

  只要记住以上的装饰器工做原理,你即可以知道如何写出带有参数的装饰器,如:

 1 def newdecorator(i):
 2     def decorator(func):
 3         def restructure(x):
 4             func(x)
 5             print('this is decorator %s%s'%(i,x))
 6         return restructure
 7     return decorator
 8 
 9 @newdecorator('?')
10 def target(x):
11     print('this is target %s'%x)
12 
13 target('!')
14 -------------------------------------------------------
15 结果:
16 this is target !
17 this is decorator ?!

  以上代码其实是:

1 target(x) == newdecorator(i)(target)(x) == decorator(target)(x) == reconstructure(x)

  同理,为了知足不一样数量的参数传入,你也能够将newdecorator(i)写成newdecorator(*i, **ii)。

6. 结束语

  只要明白装饰器的设计原理,即可以自如地写出想要的装饰器,哪怕是多重、多参数的装饰器。

 

原文:http://blog.csdn.net/sxw3718401/article/details/39519587?utm_source=tuicool&utm_medium=referral

相关文章
相关标签/搜索