跳轉到內容

Python 程式設計/裝飾器

來自華夏公益教科書


在軟體中,重複程式碼被認為是一種不好的做法,主要原因是它需要更多的工作量來維護。如果同一個演算法對不同的資料執行兩次,你可以將演算法放入一個函式中並將資料傳遞給它,以避免重複程式碼。但是,有時你會發現程式碼本身發生了變化,但兩個或多個地方仍然有大量的重複樣板程式碼。一個典型的例子可能是記錄

def multiply(a, b):
    result = a * b
    log("multiply has been called")
    return result

def add(a, b):
    result = a + b
    log("add has been called")
    return result

在這種情況下,如何消除重複並不明顯。我們可以遵循我們之前將公共程式碼移到函式中的模式,但是用不同的資料呼叫函式不足以產生我們想要的不同行為(加法或乘法)。相反,我們必須將一個函式傳遞給公共函式。這涉及一個對函式進行操作的函式,稱為高階函式

Python 中的裝飾器是高階函式的語法糖。

屬性裝飾器的最小示例

>>> class Foo(object):
...     @property
...     def bar(self):
...         return 'baz'
...
>>> F = Foo()
>>> print(F.bar)
baz

上面的示例實際上只是像這樣的程式碼的語法糖

>>> class Foo(object):
...     def bar(self):
...         return 'baz'
...     bar = property(bar)
...
>>> F = Foo()
>>> print(F.bar)
baz

通用裝飾器的最小示例

>>> def decorator(f):
...     def called(*args, **kargs):
...         print('A function is called somewhere')
...         return f(*args, **kargs)
...     return called
...
>>> class Foo(object):
...     @decorator
...     def bar(self):
...         return 'baz'
...
>>> F = Foo()
>>> print(F.bar())
A function is called somewhere
baz

裝飾器的良好用途是允許你重構你的程式碼,以便可以將常見功能移動到裝飾器中。例如,假設你想跟蹤對某些函式的所有呼叫,並打印出每次呼叫時所有函式引數的值。現在你可以在裝飾器中實現它,如下所示

#define the Trace class that will be 
#invoked using decorators
class Trace(object):
    def __init__(self, f):
        self.f =f

    def __call__(self, *args, **kwargs):
        print("entering function " + self.f.__name__)
        i=0
        for arg in args:
            print("arg {0}: {1}".format(i, arg))
            i =i+1
            
        return self.f(*args, **kwargs)

然後,你可以透過以下方式將裝飾器用於你定義的任何函式

@Trace
def sum(a, b):
    print "inside sum"
    return a + b

執行此程式碼,你會看到類似的輸出

>>> sum(3,2)
entering function sum
arg 0: 3
arg 1: 2
inside sum

或者,你可以使用函式而不是將裝飾器建立為類。

def Trace(f):
    def my_f(*args, **kwargs):
        print("entering " +  f.__name__)
        result= f(*args, **kwargs)
        print("exiting " +  f.__name__)
        return result
    my_f.__name = f.__name__
    my_f.__doc__ = f.__doc__
    return my_f

#An example of the trace decorator
@Trace
def sum(a, b):
    print("inside sum")
    return a + b

#if you run this you should see
>>> sum(3,2)
entering sum
inside sum
exiting sum
5

請記住,返回函式或一個合適的裝飾替換函式是一個好的做法,這樣就可以對裝飾器進行鏈式呼叫。

華夏公益教科書