裝飾器
對於某個函數或類,需要增添大量類似的需求。對於類來說,可以用繼承解決,但是繼承會增加大量的靜態屬性,子類會變得越來越臃腫;對於函數來說,每個需求增加一個函數封裝過於繁瑣。
裝飾器的出現解決了這一問題。裝飾器本身是一個python函數,它可以讓其它函數不發生變動的情況下增加額外的功能,裝飾器的返回值是函數對象。
直觀來說,裝飾器就是爲已有代碼增添新的功能。
原代碼:
def foo():
print('i am foo')
此時,希望增加打印日誌功能
使用函數封裝:
def use_logging(func):
logging.warn("%s is running" % func.__name__)
func()
use_logging(foo)
使用裝飾器:
def use_logging(func):
def warpper(*args,**kwargs):
loggin.warn("%s is running"% func.__name__)
return func(*args,**kwargs)
return warpper
foo = use_logging(foo)
foo()
語法糖@可以幫助我們減少一次函數定義:
def use_logging(func):
def warpper(*args,**kwargs):
loggin.warn("%s is running"% func.__name__)
return func(*args,**kwargs)
return warpper
@use_logging # 對要擴展功能的函數使用語法糖@
def foo():
print('i am foo')
foo()
語法糖@幫我們省去了foo = use_logging(foo)這一句。
帶參裝飾器
使用裝飾器時,可以對裝飾器傳遞參數,如@use_logging(level)
def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
logging.warn("%s is running" % func.__name__)
return func(*args)
return wrapper
return decorator
@use_logging(level="warn")
def foo(name='foo'):
print("i am %s" % name)
foo()
上面的use_logging是允許帶參數的裝飾器。它實際上是對原有裝飾器的一個函數封裝,並返回一個裝飾器。我們可以將它理解爲一個含有參數的閉包。當我 們使用@use_logging(level=“warn”)調用的時候,Python能夠發現這一層的封裝,並把參數傳遞到裝飾器的環境中。
類裝飾器
相比函數裝飾器,類裝飾器具有靈活度大、高內聚、封裝性等優點。使用類裝飾器還可以依靠類內部的__call__方法,當使用 @ 形式將裝飾器附加到函數上時,就會調用此方法。
class Foo(object):
def __init__(self, func):
self._func = func
def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')
@Foo
def bar():
print ('bar')
bar()
functools.wraps
使用裝飾器極大地複用了代碼,但是他有一個缺點就是原函數的元信息不見了,比如函數的docstring、__name__、參數列表:
def logged(func):
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
該函數完成等價於:
def f(x):
"""does some math"""
return x + x * x
f = logged(f)
不難發現,函數f被with_logging取代了,當然它的docstring,__name__就是變成了with_logging函數的信息了。
functools.wraps能解決這一問題。wraps本身也是一個裝飾器,它能把原函數的元信息拷貝到裝飾器函數中,這使得裝飾器函數也有和原函數一樣的元信息了。
from functools import wraps
def logged(func):
@wraps(func) # add
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
print f.__name__ # prints 'f'print f.__doc__ # prints 'does some math'
內置裝飾器
@staticmathod、@classmethod、@property
裝飾器的順序:
@a
@b
@c
def f ():
等效於f = a(b(c(f)))
偏函數
偏函數可以設定函數的默認行爲.
例子:通過設定參數來讓int()函數默認轉換成八進制數字
# 首先導入 functools 模塊
import functools
# 定義一個默認轉換爲二進制的int函數
int2 = functools.partial(int, base=2)
# 調用
int2('10110101')
181
functools.partial的作用就是將固定一個函數的默認行爲,從而簡化之後的使用
這個方法可以接收函數、*args、**kwargs這些對象
當函數的參數個數太多,需要簡化時,使用functools.partial可以創建一個新的函數,這個新函數可以固定住原函數的部分參數,從而在調用時更簡單。
參考: