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)
參考文獻
[1]. David Beazley, Brian K. Jones. Python Cookbook 3rd Edition
[2]. Wesley Chun. Core Python Applications Programming 2nd Edition