Python Decorator的来龙

引言

本文主要梳理了Python decorator的实现思路,解释了为何Python decorator是如今这个样子。设计模式

关于代理模式、装饰模式

设计模式中常常提到的代理模式、装饰模式,这两种叫法其实是说的同一件事,只是侧重点有所不一样而已。app

这二者都是经过在原有对象的基础上封装一层对象,经过调用封装后的对象而不是原来的对象来实现代理/装饰的目的ide

例如:(以Java为例)函数

public class CountProxy implements Count {
    private CountImpl countImpl;

    public CountProxy(CountImpl countImpl) {
        this.countImpl = countImpl;
    }

    @Override
    public void queryCount() {  
        System.out.println("事务处理以前");
        // 调用委托类的方法;
        countImpl.queryCount();
        System.out.println("事务处理以后");
    }

    @Override
    public void updateCount() {
        System.out.println("事务处理以前");
        // 调用委托类的方法;
        countImpl.updateCount();
        System.out.println("事务处理以后");

    }

}

在这个例子中CountProxy是对CountImpl的封装。
使用者经过CountProxy.queryCount方法来调用CountImpl.queryCount方法,这被称为代理,即CountProxy是代理类,CountImpl是被代理类。
CountProxy.queryCount方法中,能够在CountImpl.queryCount方法调用以前和以后添加一些额外的操做,被称为装饰,即CountProxy是装饰类,CountImpl是被装饰类。this

若是强调经过CountProxyCountImpl进行代理的做用,则称为代理模式;
若是强调经过CountProxyCountImpl增长额外的操做,则称为装饰模式;设计

不管是哪一种称呼,其本质都在于对原有对象的封装。
其封装的目的在于加强所封装对象的功能或管理所封装的对象。代理

从上面的例子也能够发现,代理/封装所围绕的核心是可调用对象(好比函数)。code

Python中的代理/装饰

Python中的可调用对象包括函数、方法、实现了__call__方法的类。
Python中的函数也是对象,能够做为高阶函数的参数传入或返回值返回。
所以,当代理/装饰的对象是函数时,可使用高阶函数来对某个函数进行封装。
例如:对象

def query_count_proxy(fun, name, age):
    print('do something before')
    rv = fun(name, age)
    print('do something after')
    return rv


def query_count(name, age):
    print('name is %s, age is %d' % (name, age))


query_count_proxy(query_count, 'Lee', 20)

可是,这个例子中,query_count函数做为参数传入query_count_proxy函数中,并在query_count_proxy函数中被调用,其结果做为返回值返回。这就完成了代理的功能,同时,在调用query_count函数的先后,咱们还增长了装饰代码。
可是,query_count_proxy的函数参数与query_count不同了,理想的代理应该保持接口一致才对。接口

为了保持一致,咱们能够利用高阶函数能够返回函数的特色来完成:

def query_count_proxy(fun):

    def wrapper(name, age):
        print('do something before')
        rv = fun(name, age)
        print('do something after')
        return rv

    return wrapper


def query_count(name, age):
    print('name is %s, age is %d' % (name, age))


query_count_proxy(query_count)('Lee', 20)

修改后的例子,query_count_proxy仅负责接受被代理的函数query_count做为参数,同时,返回一个函数对象wrapper做为返回值,真正的封装动做在wrapper这个函数中完成。

此时,若是调用query_count_proxy(query_count)就获得了wrapper函数对象,则,执行query_count_proxy(query_count)('Lee', 20)就至关于执行了wrapper('Lee', 20)

可是能够看到,query_count_proxy(query_count)('Lee', 20)这种使用方法,仍然不能保证一致。

为了保持一致,咱们须要利用Python中对象与其名称能够动态绑定的特色。
不使用query_count_proxy(quer_count)('Lee', 20)来调用代理函数,而是使用下面两句:

query_count = query_count_proxy(query_count)
query_count('Lee', 20)

执行query_count_proxy(query_count)生成wrapper函数对象,将这个对象经过query_count = query_count_proxy(query_count)绑定到query_count这个名字上来,这样执行query_count('Lee', 20)时,其实执行的是wrapper('Lee', 20)

这么作的结果就是:使用代理时调用query_count('Lee', 20)与不使用代理时调用query_count('Lee', 20)对使用者而言保持不变,不用改变代码,可是在真正执行时,使用的是代理/装饰后的函数。

这里,基本利用Python的高阶函数及名称绑定完成了代理/装饰的功能。
还有什么不理想的地方呢?
对,就是query_count = query_count_proxy(query_count),由于这句既不简洁,又属于重复工做。
Python为咱们提供了语法糖来完成这类的tedious work。
方法就是:

@query_count_proxy
def query_count(name, age):
    return 'name is %s, age is %d' % (name, age)

query_count = query_count_proxy(query_count)就等同于在定义query_count函数的时候,在其前面加上@query_count_proxy

Python看到这样的语法,就会自动的执行query_count = query_count_proxy(query_count)进行name rebinding

补充

以上就是Python实现可调用对象装饰的核心。
可调用对象包括函数、方法、实现了__call__方法的类,上述内容只是针对函数来解释,对于方法、实现了__call__方法的类,其基本原理相同,具体实现略有差异。

本文系做者原创,若有转载请注明出处。因为水平精力有限,若有错误欢迎指正。

相关文章
相关标签/搜索