1. 程式人生 > >python的裝飾器(裝飾者模式)

python的裝飾器(裝飾者模式)

1. 裝飾者模式

裝飾者模式是常用的軟體設計模式之一。通過此設計模式,我們能夠在不修改任何底層程式碼情況下,給已有物件賦予新的職責。python中可以用裝飾器簡單地實現裝飾者模式。

1.1 將函式作為引數傳遞

C/C++中,函式指標可以將函式作為引數傳遞給另一函式。而在python中,函式也是物件的一種,函式可以被引用,也可直接作為引數傳入函式,以及作為容器物件的元素。python中可以採用如下方法實現裝飾者模式:

#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-

def add(x, y): result = x+y return result

def

log(func): def wrapper(*args, **kwargs): result = func(*args) print(func.name,‘has been called\n’) return result return wrapper

if name == main: print(log(add)(1,2))

上述程式碼中,log函式以需要被裝飾的函式作為引數,並返回函式物件。被返回的函式的引數為可變引數*args**kwargs*args引數會被封裝成tuple**kwargs引數則會被封裝成字典物件),以適應不同函式的不同引數,保證通用性。

1.2 裝飾器

上面的實現方法有些繁雜,所有呼叫被裝飾的函式之處的程式碼,都要進行相應修改,自然不符合python簡潔易讀的特性。因此python中給出相應語法糖來增加可讀性和易用性,那便是“裝飾器”。

#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-

from functools import wraps

def log(func): #@wraps(func) def wrapper(*args, **kwargs): result = func(*args) print(func.name,‘has been called’) return result return

wrapper

#等價於add = log(add) @log def add(x, y): result = x+y return result

if name == main: print(add(1,2)) print(add.name)

執行情況如下

>>print(add(1,2))
add has been called
3
>>print(add.__name__)
wrapper

但上述方法亦有缺陷,原函式add的元資料(比如名字、文件字串、註解和引數簽名)會丟失。為避免缺陷,任何時候你定義裝飾器的時候,都應該使用functools庫中的@wraps裝飾器來註解底層包裝函式(程式碼中註釋部分)。@wraps有一個重要特徵是它能讓你通過屬性 __wrapped__ 直接訪問被包裝函式。 改進後執行情況

>>print(add(1,2))
add has been called
3
>>print(add.__name__)
add

1.3 解除裝飾器

當裝飾器已經作用於某函式,而你想撤銷它,那麼可以訪問 __wrapped__屬性來訪問原始函式

orig_add = add.__wrapped__
orig_add(1,2)

但若使用了多個裝飾器, __wrapped__屬性會變得不可控,應儘量避免使用。 若有如下程式碼:

#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-

import functools import time

def metric(func): @functools.wraps(func) def wrapper(*args,**kv): print(‘Decorator1’) f = func(*args,**kv) return f return wrapper

def logging(func): @functools.wraps(func) def wrapper(*args,**kv): print(‘Decorator2’) f = func(*args,**kv) return f return wrapper

@metric @logging def normalize(name): sName = name[0:1].upper() + name[1:].lower() print(sName)

if name == main: normalize(‘heLlO’) normalize.wrapper(’’)

執行情況如下

>>normalize('helLo')
Decorator1
Decorator2
Hello
>>normalize.__wrapped__('world')
Decorator2
World

1.4 定義帶引數的裝飾器

from functools import wraps

def log(text): def decorator(func): @wraps(func) def wrappering(*args,**kv): print(’%s %s():’%(text,func.name)) return func(*args,**kv) return wrappering return decorator

@log(‘run’) def normalize(name): sName = name[0:1].upper() + name[1:].lower() print(sName)

裝飾器函式可以帶引數,最外層的函式會將引數傳給內層的裝飾器函式,即wrappering函式是可以使用log的傳入引數的。 裝飾器處理過程與下面是等價的:

normalize = log('run')(normalize)