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__方法的類,其基本原理相同,具體實現略有差異。

本文系做者原創,若有轉載請註明出處。因爲水平精力有限,若有錯誤歡迎指正。

相關文章
相關標籤/搜索