Python修饰器的用途可以这样简单的理解,假如我们已经定义了一个对象,如函数F(),并且在代码中完成了相关使用,即已经对函数进行了调用,之后又想对F进行一些修改。方法之一是直接修改F,而不用修改其后对F的调用。但如果想依然保留F的源代码不用修改,在Python中我们可以利用函数是对象可以直接赋值的特性,另外编写一个函数D,进行如下的操作:
def F: #F的原始定义,早已经完成
...
def D: #重新定义一个新函数D,与F类似
...
F=D #在F原来的调用之前重新将F指向D,以后的调用实际就修改为D了
... #以前的代码,其中有对F的调用
当然,编写D的另一个好处是如果这种修改是一种对与F类似的系列对象都可能发生的公共的修改,在一次编写D后可以多次使用类似的方法对F1、F2……进行修改,从而提高代码复用率。
为了方便这种处理,Python修饰器其实只是提供一种语法上看起来简洁的方法,首先定义公共的修改方法D即修饰器,并让其接收被修改对象F等为参数,然后利用@D的修饰器语法形式在定义F系列函数时让其直接完成这种替换与修改,不用再写F=D这一语句,从语法和逻辑上显得更为简洁。不过单从这种使用场景看,个人觉得这种语法上的简洁其实反而不一定让人理解,因此实际上这种方法必须对F等对象在定义时进行修改,如果要改回来,反而不如添加和注释F=D这一语句来得方便。当然,如果有某种条件能够预先明确的知道什么时候需要执行原始的F,什么时候需要对F进行特定的修改,直接在D中根据情况或者返回F本身,或者返回对F的相应修改也是能够提高方便性和代码复用率的。但最为可惜的是由于替换是在定义(或者说编译)时发生的,所以这种应用场景基本没有可能了,因为解释器的条件选择计算仅仅在定义和翻译只进行了一次,其结果是最终修饰后的对象函数实际上只选择了D中的某一个具体的条件选择下的代码对F进行了修改,之后D代码中表面看起来的选择已经不存在了。要想在运行的时候实现动态选择,必须由修改后的函数本身来进行,如下例:
def D1(f): #定义修饰器,试图通过判断f()的值来确定对f进行怎样的修改
if f()>3: return lambda:2*f()
else: return lambda:f()
@D1
def F():
return eval(input('Input:'))
上述代码看起来想在F中直接由键盘接收一个值并返回,而在D1中根据键盘接收的值是否大于3来判断是否对键盘输入值乘以2。这是一个在运行F时获取键盘输入值并动态返回对输入值响应的过程。但实际上当被修饰函数F一定义完毕,解释器就会立即执行修饰器函数并调用F从键盘获得一个输入值,若这时的输入值大于3,则函数F被固定的修改为返回键盘值的2倍,否则被固定为返回键盘输入值。此后在你运行F函数输入的值是否大于3,结果都被固定下来了。
要想具有动态效果,其条件判断必须由修改后的函数来执行,如下例:
def D2(f): #定义修饰器,由修饰器修改的函数来完成条件判断,获得动态效果
def dF():
v=eval(input('Input:'))
if v>3: return 2*v
else: return v
return dF
@D2
def F():
return eval(input('Input:'))
上述修饰器函数实际上是完全重现了原F函数的输入功能,并根据输入值对其原有的返回值功能进行了修饰修改,或者说由dF函数完全取代了F函数,其条件判断实际是在dF中执行的,所以才能实现动态的判断,获得正确的功能。
其实Python语言本身倒是充分利用了这种公共抽象修改的特点,实现了很多其内部的语法特征。如Python类的方法分为由实例个体拥有的个体方法(一般的方法),由类拥有的类方法,以及静态方法。Python对类方法和静态方法的实现都是通过其内建的修饰器函数classmethod(cls,function)、staticmethod(function)并使用修饰器语法@classmethod和@staticmethod实现的。