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
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
#等價於add = log(add) 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): def wrapper(*args,**kv): print(‘Decorator1’) f = func(*args,**kv) return f return wrapper
def logging(func): def wrapper(*args,**kv): print(‘Decorator2’) f = func(*args,**kv) return f return wrapper
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): def wrappering(*args,**kv): print(’%s %s():’%(text,func.name)) return func(*args,**kv) return wrappering return decorator
def normalize(name): sName = name[0:1].upper() + name[1:].lower() print(sName)
裝飾器函式可以帶引數,最外層的函式會將引數傳給內層的裝飾器函式,即wrappering
函式是可以使用log
的傳入引數的。
裝飾器處理過程與下面是等價的:
normalize = log('run')(normalize)