python中的裝飾器
裝飾器是為了解決以下描述的問題而產生的方法
我們在已有的函數代碼的基礎上,想要動態的為這個函數增加功能而又不改變原函數的代碼
例如有三個函數:
def f1(x): return x def f2(x): return x*x def f3(x): return x*x*x
而我們想為這三個函數增加一個函數調用打印功能 類似print("call f1()")
如果我們直接修改的話,需要對每個函數的內部進行改寫。
所以為了簡化代碼,我們可以使用python內置的@裝飾器的方法,可以做到修飾函數的功能
Python的 decorator 本質上就是一個高階函數,它接收一個函數作為參數,然后,返回一個新函數。
裝飾器可以極大地簡化代碼,避免每個函數寫重復性代碼
不帶參數的decorator
例如我們可以編寫一個@log可以打印函數調用的裝飾器
def log(f): def fn(x): print ('call ' + f.__name__ + '()...') return f(x) return fn
然后我們可以測試一下
@log def f1(x): return x a=f1(1)
print(a)
結果
call f1()...
1
但是,對於參數不是一個的函數,調用將報錯:
@log def add(x, y): return x + y print (add(1, 2))
Traceback (most recent call last): File "D:/pythonwork/cvworktest/final/practice.py", line 12, in <module> print (add(1, 2)) TypeError: fn() takes 1 positional argument but 2 were given
這是因為add() 函數需要傳入兩個參數,但是 @log 寫死了只含一個參數的返回函數。
要讓 @log 自適應任何參數定義的函數,可以利用Python的 *args 和 **kw,保證任意個數的參數總是能正常調用:
def log(f): def fn(*args, **kw): print ('call ' + f.__name__ + '()...') return f(*args, **kw) return fn
現在,對於任意函數,@log 都能正常工作。
帶參數decorator
上面的@log不帶任何參數,同樣我們可以編寫一個帶參數的decorator
例如如果有的函數非常重要,希望打印出'[INFO] call xxx()...',有的函數不太重要,希望打印出'[DEBUG] call xxx()...',這時
log函數本身就需要傳入'INFO'或'DEBUG'這樣的參數,類似這樣:
@log('DEBUG') def my_func(): pass
我們把它翻譯成高階函數就是這樣:
my_func = log('DEBUG')(my_func)
展開:
log_decorator = log('DEBUG') my_func = log_decorator(my_func)
又相當於:
log_decorator = log('DEBUG') @log_decorator def my_func(): pass
所以,帶參數的log函數首先返回一個decorator函數,再讓這個decorator函數接收my_func並返回新函數:
def log(content): def log_decorator(f): def fn(*args, **kw): print ('[%s] %s()...' % (content, f.__name__)) return f(*args, **kw) return fn return log_decorator @log('DEBUG') def test(): pass print (test())
結果:
[DEBUG] test()...
None
decorator的注意事項:
經過@decorator“改造”后的函數,和原函數相比,除了功能多一點外,還有很重要的一點就是函數自身的改變
在沒有decorator的情況下,打印函數名:
def f1(x): pass print (f1.__name__)
結果:
f1
有decorator的情況下,再打印函數名:
def log(f): def wrapper(*args, **kw): print ('call...') return f(*args, **kw) return wrapper @log def f2(x): pass print (f2.__name__)
結果:
wrapper
由於decorator返回的新函數函數名已經不是'f2',而是@log內部定義的'wrapper'。這對於那些依賴函數名的代碼就會失效。decorator還改變了函數的__doc__等其它屬性。如果要讓調用者看不出一個函數經過了@decorator的“改造”,就需要把原函數的一些屬性復制到新函數中:
def log(f): def wrapper(*args, **kw): print ('call...') return f(*args, **kw) wrapper.__name__ = f.__name__ wrapper.__doc__ = f.__doc__ return wrapper
這樣寫decorator很不方便,因為我們也很難把原函數的所有必要屬性都一個一個復制到新函數上,所以Python內置的functools可以用來自動化完成這個“復制”的任務:
import functools def log(f): @functools.wraps(f) def wrapper(*args, **kw): print ('call...') return f(*args, **kw) return wrapper
注意:對於函數的參數信息哦我們無法確定,因為裝飾器與原函數的參數名不一定一樣