Python裝飾器探究——裝飾器參數

探究裝飾器參數

編寫傳參的裝飾器

通常我們見到的簡單裝飾器這樣的:

import json
import functools

def json_output(func):
    @functools.wraps(decorated)
    def inner(*args, **kwargs):
        result = func(*args, **kwargs)
        return json.dumps(result)
    return inner

@json_output
def f():
    return {'status': 'done'}

當裝飾器應用於函數 f 上時,它接受 f 作爲其參數,返回一個函數 inner ,且將他綁定到變量f上。

示例中我們編寫的裝飾器 json_output 只接受一個隱式參數——即被裝飾的方法,在使用此裝飾器時本身看上去是並沒有參數的。然而有時候需要讓裝飾器自身帶有一些需要的信息,從而使裝飾器可以使用恰當的方式裝飾方法。比如上面的例子中,我們想通過向裝飾器傳入不同的參數來控制輸出結果的縮進(indent)和排序(sort)。我們可以這麼做:

import json
import functools

def json_output(indent=None, sort_keys=False):
    def actual_decorator(func):
        @functools.wraps(func)
        def inner(*args, **kwargs):
            result = func(*args, **kwargs)
            return json.dumps(result, indent=indent, sort_keys=sort_keys)
        return inner
    return actual_decorator

@json_output(indent=4)
def f():
    return {'status': 'done'}

理解傳參的裝飾器

初次看起來會覺得比較繞人,因爲函數裏嵌套了兩個函數定義,然而實際上和之前一個版本的區別在於爲了接收json序列化的參數多包裝了一層,所以

@json_output(indent=4)
def f():
    return {'status': 'done'}

# 相當於
@actual_decorator
def f():
    return {'status': 'done'}

這樣看起來就會明晰很多。

實際上, 裝飾器裏的 @ 後接收一個函數,該函數以被裝飾的函數(例子中是f)爲參數,並且返回一個函數。當需要在裝飾函數的同時傳入參數的話,那麼就需要多包裝一層,先傳入參數(例子中是 indent=4 )返回一個裝飾的函數(例子中是 actual_decorator ), 這個返回的的函數 就跟以前一樣接受被裝飾的函數(f)作爲參數並且返回一個函數作爲裝飾最後的方法供調用。

傳參和不傳參的兼容

然而當我們像上面那樣定義裝飾器時,就不能這樣調用:

import json
import functools

def json_output(indent=None, sort_keys=False):
    def actual_decorator(func):
        @functools.wraps(func)
        def inner(*args, **kwargs):
            result = func(*args, **kwargs)
            return json.dumps(result, indent=indent, sort_keys=sort_keys)
        return inner
    return actual_decorator

@json_output
def f():
    return {'status': 'done'}

在實際的項目過程中,有時會出現這樣的狀況: 一開始寫的裝飾器時不需要使用時傳參數的,後來發現有必要傳參數,改好後原來不傳參的裝飾器不能正常使用了,這是修改原來使用的地方是項痛苦的事情。
這時候就需要對裝飾器做一個兼容,使它在以下情況都可用:

@json_output
@json_output()
@json_output(indent=4)

具體做法如下:

import json
import functools

def json_output(decorated_=None, indent=None, sort_keys=False):
    if decorated_ and (indent or sort_keys):
        raise

    def actual_decorator(func):
        @functools.wraps(func)
        def inner(*args, **kwargs):
            result = func(*args, **kwargs)
            return json.dumps(result, indent=indent, sort_keys=sort_keys)
        return inner
    if decorated_:
        return actual_decorator(decorated_)
    else:
        return actual_decorator


@json_output(indent=4)
def f1():
    return {'status': 'done'}

@json_output
def f2():
    return {'status': 'done'}

@json_output()
def f3():
    return {'status': 'done'}

print f1()
print f2()
print f3()

代碼中關鍵的地方在於 json_output 在最後對參數 decorated 進行了判斷,有的話證明是不傳參調用,那麼直接返回 actual_decorator 的調用;沒有的話則代表是傳參類型的調用(雖然參數可能不存在),那麼返回 actual_decorator 。其中有點需要注意, josn_output 的傳參需要使用關鍵字參數,如果像下面這樣直接傳一個位置參數,那麼根據現在的實現會出現錯誤(因爲它會被當成 decorated_ )。

@json_output(4)  #錯誤的使用方法
def f4():
    return {'status': 'done'}

參考資料

  • 《Python高級編程》 by Luke Sneeriger
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章