[Python] 对 Python 装饰器的理解心得

http://www.cnblogs.com/ifantastic/archive/2012/12/09/2809325.html

最近写一个py脚本来整理电脑中的文档,其中需要检校输入的字符,为了不使代码冗长,想到使用装饰器。

  上网搜索有关python的装饰器学习文档,主要看的是AstralWind的一篇博文,以及Limodou的一篇文章。作为初学者,这两篇文章对新手有很大的帮助,但仍然有些不易理解的地方。因此在此以一个初学者的认知记录一下python的装饰器的学习心得。

 

  1. 什么是装饰器?

 

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

  当你明白什么是装饰器之后,自然会觉得这个名字取得恰如其分,但作为初学者来说多少还是会有些迷茫。下面用代码来说明怎么理解装饰器。

 

#脚本1
def target():
    print('this is target')

def decorator(func):
    func()
    print('this is decorator')

decorator(target)
-------------------------------------------

运行结果为:

this is target
this is decorator

Python允许将方法当作参数传递,因此以上脚本就是将target方法作为参数传入decorator方法中,这其实也是装饰器的工作原理,以上代码等同于:

#脚本2
def decorator(func):
    func()
    print('this is decorator')

@decorator
def target():
    print('this is target')

target
-------------------------------------------

运行结果:

this is target
this is decorator

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

 

@decorator
def target():
    ...

 

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

 

  2. 装饰器的原理

 

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

#脚本3

def decorator(func):
    func()
    print('this is decorator')

@decorator
def target():
    print('this is target')

target()
--------------------------------------------

运行结果:

this is target
this is decorator
Traceback (most recent call last):
  File "C:/Users/Me/Desktop/ff.py", line 34, in <module>
    target()
TypeError: 'NoneType' object is not callable

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

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

 

复制代码
@decorator
def target():
    ...

等同于

def decorator(target)():
    ...

注:python语法中以上写法是非法的,以上只是为了便于理解。
复制代码

 

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

 

复制代码
#脚本2
def decorator(func):
    func()
    print('this is decorator')
  
@decorator
def target():
    print('this is target')
  
target
-------------------------------------------

实际运行情况:
首先调用decorator方法:decorator()
因为decorator方法含1个参数,因此将target传入:decorator(target)
运行代码“func()”,根据传入的参数,实际执行target(),结果打印出:this is target
运行代码"print('this is decorator')",结果打印出:this is decorator

 

   对比脚本3的运行情况:

 

#脚本3
def decorator(func):
    func()
    print('this is decorator')
  
@decorator
def target():
    print('this is target')
  
target()
-------------------------------------------

实际运行情况:
首先调用decorator方法:decorator()
因为decorator方法含1个参数,因此将target传入:decorator(target)
运行代码“func()”,根据传入的参数,实际执行target(),结果打印出:this is target
运行代码"print('this is decorator')",结果打印出:this is decorator

以上与脚本2中运行情况完全相同,接下来便是执行脚本2中target没有的(),也就是执行调用命令。
由于decorator(target)没有返回一个可以被调用的对象,因此“大脑”提示错误:'NoneType' object is not callable

 

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

@decorator
def target():
    ...

等同于
def decorator(target)():
    ...

因此:
target == decorator(target)
target() == decorator(target)()

所以:
假设有一个变量var=target,在将target赋值给var时,其实是将decorator(target)的调用结果赋值给var,因为var不具备调用性(not callable),因此执行var()时,编译器会报错它是个NoneType对象,不能调用。
 
 
 
 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值