python: 設計模式(design pattern)之修飾器模式(decorator)

JUAN425發表於2015-04-12

修飾器模式是物件導向程式設計領域的一種設計模式。 通過向一個類物件動態的新增新的行為而實現。 修飾器可以給某個物件新增一些功能, 而不是整個類新增功能, 所以很靈活。 

在python中, 我們可以使用decorators對callable的objects(注意包括functions, methods, 或者類classes)進行一些簡單的修飾, 使得這些objects具有一些新的行為。

要理解decorators, 我們必須首先知道, python中, functions也是一個物件, 就想string, variables, class , integers等一樣。 函式是根據給定的引數吐出產生的值的。

我們可以將一個函式作為一個引數傳遞給另一個函式, 也可以將一個函式賦值給一個變數, 或者函式返回一個函式。 

如下, 返回一個函式:

def foobar(original_function):
 
    # make a new function
    def new_function():
        # some code
 
    return new_function

decorator的定義:

一個decorator就是一個function, 吃callable object作為其引數, 然後將新增修飾後的callable obeject(例如函式)作為一個value返回去。

如下例就是對類進行修飾。例如, 下面就是一個修飾器:

def verbose(original_function):
 
    # make a new function that prints a message when original_function starts and finishes
    def new_function(*args, **kwargs):
        print("Entering", original_function.__name__)
        original_function(*args, **kwargs)
        print("Exiting ", original_function.__name__)
 
    return new_function

要使用decorator也很簡單。 

我們可以將一個函式傳進去, 得到新一個新的修飾後的函式talkative_widget_func。 如下:

def widget_func():
    # some code
 
talkative_widget_func = verbose(widget_func)


另外, 注意, 我們不光可以使用一個新的修飾後的函式去替換掉原有的函式, 我們還可以去得到一個增強版的原始的函式, 注意, 這不是新的函式, 而是在原來函式的基礎上加上了一層封裝, 如下:

def widget_func():
    # some code
 
widget_func = verbose(widget_func)
注意上述返回的函式命名為widget_func, 和傳進去的原始函式一樣。


除此之外, python還有一個decortation syntax。 也就是使用“@”去建立decoration lines. 這個feature就是語法糖(syntax sugar), 是的我們可以將上面的例子重新寫為如下形式:

@verbose
def widget_func():
    # some code

上述產生的結果和上面的增強版本一樣了。 這樣我們的新的widget_func函式就具有原始的widget_func所有的行為在加上varbose修飾器新增的行為了。

不光可以修飾函式, 也可以對類進行修飾。程式如下:

#!/usr/bin/python

#decotor design pattern

import sys

YELLOW = '\033[93m'
RED = '\033[91m'
NORMAL = '\033[0m'

class Person(object):
    def __init__(self, name, age): # constructor
        self.name = name
        self.age = age
    
    def __str__(self):
        return '%s is %s' %(self.name, self.age)
# decorator class to wrapps an object passed in
#for age < 20, print NOEMAL
#for 20 < age < 20, print YELLOW
#for age > 30, print RED
class PersonDecorator(Person):
    def __init__(self, person):
        self._person = person
        
    def __getattr__(self, name):
        return getattr(self._person, name)
        
    def __str__(self):
        age = self._person.age
        colour = NORMAL
        if age >= 30:
            colour = RED
        elif age >= 20:
            colour = YELLOW
        
        return '%s%s%s' %(colour, self._person.__str__(), NORMAL)

def main():
    p = [] # list of person
    p.append(Person('Micheal', 25))
    p.append(Person('Kate', 2))
    p.append(Person('Mark', 48))
    p.append(Person('Matt', 21))
    
    for person in p:
        if '-c' in sys.argv:
            person = PersonDecorator(person)
        print person
        
if __name__ == '__main__':
    main()
執行結果如下:


附錄: 解釋(stackoverflow摘錄)

*args and **kwargs

語法* 和 **預設使用的。 基本上學習python的時候用不上。
當你不確定函式中應該傳進去幾個引數的時候,可以使用它。 即it allows you to pass an arbitary number of arguments to your function:
如下:

Similarly, **kwargs allows you to handle named arguments that you have not defined in advance:

如下:

>>> def table_things(**kwargs):
...     for name, value in kwargs.items():
...         print '{0} = {1}'.format(name, value)
...
>>> table_things(apple = 'fruit', cabbage = 'vegetable')
cabbage = vegetable
apple = fruit
You can also use both in the same function definition but *args must occur before **kwargs.

http://www.wklken.me/posts/2013/12/21/how-to-use-args-and-kwargs-in-python.html

解釋如下:在函式中使用*和**是用於傳遞可變長引數, *args用於傳遞非命名剪枝可變長引數列表。 **kwargs用作傳遞鍵值可變長引數列表。 如下, 傳遞一個位置引數和兩個可變長引數。

def test_var_args(farg, *args):
    print "formal arg:", farg
    for arg in args:
        print "another arg:", arg

test_var_args(1, "two", 3)





def test_var_kwargs(farg, **kwargs):
    print "formal arg:", farg
    for key in kwargs:
        print "another keyword arg: %s: %s" % (key, kwargs[key])

test_var_kwargs(farg=1, myarg2="two", myarg3=3)

執行結果如下:

上述語法也可以出現在呼叫函式中, 如下:

def test_var_args_call(arg1, arg2, arg3):
    print "arg1:", arg1
    print "arg2:", arg2
    print "arg3:", arg3

args = ("two", 3)
test_var_args_call(1, *args)
如下:


或者:

def test_var_args_call(arg1, arg2, arg3):
    print "arg1:", arg1
    print "arg2:", arg2
    print "arg3:", arg3

kwargs = {"arg3": 3, "arg2": "two"} #ie dictionary
test_var_args_call(1, **kwargs)


相關文章