tufeiping.github.io

涂飞平的个人博客

Follow me on GitHub

Python的Decorator

原创 涂飞平 2018-10-23

pythodecorator.png

Python语言中的Decorator,也就是常说的装饰器,个人认为是Python语言中的亮点之一,在语言层面(从语义上)直接支持装饰器的我所知道的也就是Python了,虽然JavaScript也是支持的,但需要通过技术手段来处理一下(都是将函数赋值给同名的变量以达到相同的效果),而Python虽然在原理上是一致的,但其在语言层面直接支持,提供了 @ 运算符来标识(跟Java提供的注解采用一样的标识符,而且作用也类似),更直观,更简洁!

对于Decorator的作用,可以简单概括为:函数对象的偷梁换柱!

就是将一个新函数替换原来的函数,由于PythonJavaScript中的函数都是对象(First Class Object),所以可以赋值给指定的变量,这个变量名 就是我们所说的函数名。这有什么用处呢?

  1. 函数补丁,即修改原有函数的bug,在作为框架或者产品代码的库中,也许已经大量调用了某个函数,但该函数有问题,这个时候,如果能重新修改该函数,且不影响原有代码,Decorator就可以解决该问题;

  2. 扩展函数功能,可以扩展原有函数的功能(熟悉Spring的就能知道,这其实就是AOP的思想),如果你想给你已经写好的函数添加一些新的功能(比如调用日志) 你可以使用新的函数替换原有函数(当然,在新的函数中还是要调用原有的函数),然后将该新函数替换原有函数就可以解决这个问题,Decorator也是完美的解决方案。

JavaScript中,我们需要扩展一个名为test的函数,比如

var test = function() {
    console.log("hello world");
}

给它加上日志功能,代码如下

decorator = function(fn/*你看,这里函数就是作为参数进行传递*/) {
    return function(){
        console.log("before test");
        fn();
        console.log("after test");
    }
}

然后,替换的时候,只要完成一次调用即可:

test = decorator(test);

其中的fn就是传入的函数对象,在返回的新的函数对象中,fn会保存在作用域链中,(闭包的原理,同样适用于Python

而调用的时候,完全可以不用去理会其中函数的细节是否发生变化

//...其他代码 
test(); 
//...其他代码

Python中,也可以举出一模一样的例子来:

def test():
    print "hello world"
	

也给他加上日志功能,代码如下:

def decorator(fn):
    def newTest():
        print "before"
        fn()
        print "after"
    return newTest

类似的替换方式:

test = decorator(test)

然后,也是同样的调用方式:

#...其他代码 
test() 
#...其他代码

Python中,可以用语法糖 @ 来简化代码,使得代码更简单优雅,比如上面的代码:

test = decorator(test)

可以参考一下,下面的代码其实就等效于上面的代码,看起来是不是简洁很多了?

@decorator 
def test():

了解原理之后,就可以理解Python库中的各种装饰器调用,概括起来:使用新的函数对象来包装旧的函数对象,以扩展或者替换原有对象。

下面简单分析几种Python中的装饰器使用方式:

  1. 简单的装饰器,没有参数,只是做简单的工作,如上面的例子所示;

  2. 带有参数的装饰器,带有参数,根据参数来修改工作函数的行为,比如Bottle中使用Route装饰器来分发访问请求(以下代码摘自http://sunnytu.sinaapp.com的,该应用采用Bottle /* 备注:这是很早以前,我在新浪云部署的应用,采用的是Bottle Web框架,目前已经废弃了 */)

@app.route('/html/:fn') 
def sundy_static(fn): 
    return static_file(fn, root="static")

过多的分析,可能会把人搞晕,我还是直接写出route的示意代码吧:

def route(url_pattern):
    def newroute(fn):
        def newproc():
            #process with url_pattern, get params
            params = xxx
            return fn(params)
        return newproc
    return newroute

而@route(‘/html/:fn’)所做的工作如下:

sundy_static = route('/html/:fn')(sundy_static)

Bottle会将原始的 sundy_static 加入路由列表,以便在处理请求的时候找到所需的处理例程,加入参数后的装饰器更加强大,与AOP表达式类似,可以进行逻辑处理(如何替换旧的函数对象,或者生产新的对象);

  1. 对象方法的装饰器,其实与一般方法类似,只是要注意特殊参数self,比如:
class test(): 
    def sayHello(self): 
        print "Hello world"


def decorator(fn): 
    def newHello(self): 
        print "before" 
        fn(self) 
    return newHello

如何使用呢? 直接在类定义中,方法前面加入该装饰器即可:

class test(): 
    @decorator 
    def sayHello(self):

Python有一些比较常用的Decorator,比如staticmethod, classmethod等 关于闭包,你可以参考这个链接(http://developer.51cto.com/art/201006/208139.htm)。

Decorator应用模式,其实就是代理模式,也是实现AOP(面向切/方面编程)的基础。

JavScriptPython是原生支持AOP的,JavaC#可以通过动态代理或者第三方工具可以达到相同的效果。