1. 程式人生 > >python設計模式之裝飾器模式

python設計模式之裝飾器模式

目錄

定義:

總結:

定義:

修飾模式,是面向物件程式設計領域中,一種動態地往一個類中新增新的行為的設計模式。就功能而言,修飾模式相比生成子類更為靈活,這樣可以給某個物件而不是整個類新增一些功能。 通過使用修飾模式,可以在執行時擴充一個類的功能。原理是:增加一個修飾類包裹原來的類,包裹的方式一般是通過在將原來的物件作為修飾類的建構函式的引數。裝飾類實現新的功能,但是,在不需要用到新功能的地方,它可以直接呼叫原來的類中的方法。修飾類必須和原來的類有相同的介面。

 裝飾器(Decorator)模式是我使用率很高的一種模式,一旦熟悉了, 你就會愛上它.

裝飾器使用前:

讓我們從一個具體的問題開始:

# 編寫一個函式,獲取一個URL的內容
import urllib

def fetch(url):
    return urllib.urlopen(url).read()

上面這段程式碼很簡單,獲取一個URL的內容。但這時我們遇到了一個問題,由於網路狀況或者網站的負載等原因,有些情況下訪問會失敗,但是經過一些重試之後就可以成功。這個時候,我們就需要把程式碼做一些修改,見下面的程式碼:

import urllib
import time

def fetch(url):
    for _ in xrange(5):
        try:
            return urllib.urlopen(url).read()
        except:
            time.sleep(1)
    else:
        raise RuntimeError

 上面程式碼能解決我們之前提到的網路暫時問題, 但同時引入另外問題, 多出來的程式碼和fetch本身無關, 而增加的重試程式碼其實在很多地方都是通用的, 而它確變成這個fetch私有的程式碼. 好了,我們的裝飾器可以派上用場了.

裝飾器定義:

裝飾器是一個函式,它的使用方法是在被裝飾的函式前加上@<decorator>,從原理上說,裝飾器函式是一個接收函式作為輸入引數,返回一個新的函式的函式。見如下例子:

def decorator(f): # 輸入引數為函式
    def wrapper(*args, **kwargs): # wrapper函式,用於替代f
        print 'start in function wrapper.'
        result = f(*args, **kwargs)
        print 'end in function wrapper.'
        return result

    return wrapper # 返回wrapper函式

# 被裝飾函式
def test(name):
    print "in test function,", name

# 不用裝飾器語法
wrapper_test = decorator(test)
wrapper_test('hello')
#輸出為:
# start in function wrapper.
# in test function,hello
# end in function wrapper.

我們在上面定義了一個裝飾器函式decorator,一個被裝飾函式test,當我們需要為test函式增加功能的時候,通過decorator(test)生成一個新的函式來實現。可以看到,這樣的程式碼有點羅嗦,如果test有多個裝飾器,這裡會生成多個類似wrapper_test的新函式,所以我們渴望有一個好用的語法糖,而Python提供了。

上面的程式碼我們可以變成如下:

def decorator(f): # 輸入引數為函式
    def wrapper(*args, **kwargs): # wrapper函式,用於替代f
        print 'start in function wrapper.'
        result = f(*args, **kwargs)
        print 'end in function wrapper.'
        return result

    return wrapper # 返回wrapper函式

# 被裝飾函式
def test(name):
    print "in test function,", name

# 用裝飾器語法
@decorator
test('hello')

#輸出為:
# start in function wrapper.
# in test function,hello
# end in function wrapper.

可以程式碼對比可以看到通過@decorator裝飾後的程式碼與之前效果一樣,但是語法簡潔了很多。

使用裝飾器重構上面retry的程式碼

import urllib
import time
import functools

def retry(times, interval):
    def _retry(f):
        @functools.wraps(f)
        def wrapper(*args, **kwds):
            for _ in xrange(times):
                try:
                    return f(*args, **kwds)
                except:
                    time.sleep(interval)
            else:
                raise RuntimeError

        return wrapper

    return _retry

@retry(5, 1)
def fetch(url):
    return urllib.urlopen(url).read()

這時候,fetch基本又回到了最初的樣子,簡單明確,而retry作為一個獨立的函式則可以被很多其他地方複用,我們成功地把兩者解藕了 

常用使用case:

這就是裝飾器模式,在很多地方都有它的應用,比如最常見的property。

class MyObj(object):
    @property
    def name(self):
        return 'MyObj', self.__hash__()

 在一些library如bottle裡面也大量使用了裝飾器,如:

from bottle import route, run, template

@route('/hello/<name>')
def index(name):
    return template('<b>Hello </b>!', name=name)

run(host='localhost', port=8080)

從名字就可以看出route是一個路由裝飾器,它的實現如下:

def make_default_app_wrapper(name):
    ''' Return a callable that relays calls to the current default app. '''
    @functools.wraps(getattr(Bottle, name))
    def wrapper(*a, **ka):
        return getattr(app(), name)(*a, **ka)
    return wrapper

route     = make_default_app_wrapper('route')

總結:

裝飾器模式是讓應用解藕的一個非常好用的模式,對於認證、快取、重試等需求,用該模式可以在不改變現有程式碼邏輯的情況下新增增強功能。

但是,也需要注意的是,不是什麼程式碼都適合放在裝飾器裡面的,如果那本來就是函式邏輯的一部分,那還是放在函式內部吧,另外在做單元測試的時候,我們通常也會把裝飾器都mock掉,以方便測試。裝飾器這種場景如何mock? 我會在UT部分闡述.