1 为什么要用装饰器
在介绍装饰器以前,我们先介绍一个写代码需要遵循的原则:开发封闭原则(OCP,Open Closed Principle)。一但产品上线,就尽量避免修改源代码。但在后期,也会添加各种需求。所以在设计之初,需要预留一些接口,用来作修改的手段。装饰器也应运而生。
开发封闭原则:已经实现的功能代码不允许被修改,但可以被扩展已经实现的功能代码不允许被修改,但可以被扩展。
封闭:已实现的功能代码块
开放:对扩展开发
# 2 装饰器的作用 装饰器(decorator)的作用,**在不修改源代码以及调用方式的情况下,给源码加入一些新功能。**
3 哪些能作装饰器
任何可调用的对象都可以做装饰器,如函数、类。
那被装饰的对象又有哪些要求?
和装饰器一样,只要是可调用的对象,也都可以做被装饰对象。
4 装饰器有哪几种
先从简单的开始讲,不适用装饰器是如何实现的:
我们都知道在python中函数是第一类对象,可以作为参数,也可以赋值给其他变量。
import time
def log(func):
def swapper():
start=time.clock()
func()
end=time.clock()
print("run time is:",end-start)
return swapper
def index():
print("this is index")
index=log(index)
index()
result:
this is index
run time is: 2.285910487477409e-05
1 无参装饰器
上面的代码我们使用装饰器,就变成:
import time
def log(func): #1 #3
def swapper(): #4
#print(func.__name__) #index
start=time.clock() #8
func() #9 #11
end=time.clock() #12
print("run time is:",end-start) #13
return swapper #5
@log #index=log(index) #2 #6
def index():
#print(index.__name__) #swapper
print("this is index") #10
index() #7 #14
不难看出,@log等价于index=log(index)。其实@log的定义就是,将@正下方的函数名作为log()的参数,并作为接受返回值的对象。所以遇到@log,就把它理解为index=log(index),当执行到#5时,return swapper,也就是index=swapper,所以在第#9执行原index(),index.__name__打印的是swapper。而在第#4,形参func实际上接收的是index,所以func.__name__打印的是index。
我们再来看另一种情况,如果在index(),添加形参怎么做:
import time
def log(func):
def swapper(arg):
#print(func.__name__)
start=time.clock()
func(arg)
end=time.clock()
print("run time is:",end-start)
return swapper
@log
def index(name):
print("this is %s's index"%name)
index("abc")
result:
this is abc's index
run time is: 2.7057715974222393e-05
如果要接收任意参数,把swapper(arg)改为swapper(*args,**args),func(arg)改为func(*args,**args)
2 有参装饰器
要给内部函数传递一个或多个变量,能想到的一个闭包,一个定义全局命名空间的变量。同时又希望函数能携带这一个或多个变量的信息,只能是闭包。所以我们只需要在已有的装饰器外面再包一层函数,并把这一个或多个变量作为形参。这样内部函数就能获取到了。
import time
def log2(type):
def log(func):
def swapper(*args,**kwargs):
if type=="file":
start=time.clock()
res=func(*args,**kwargs)
end=time.clock()
print("run time is :",end-start)
return res
elif type=="sql":
print("other")
return swapper
return log
@log2(type="file") #log #home=log(home)
def home(name): #home=log(home)
print("%s 's personal page!"%name)
home("aaa")
result:
aaa 's personal page!
run time is : 2.7057715974222393e-05
3 补充
我们从“1 无参装饰器”种不难看出func.__name__和index.__name__的值并不一样。
import time
def log(func):
def swapper(*args,**kwargs):
print("function swapper:",func.__name__)
start=time.clock()
res=func(*args,**kwargs)
end=time.clock()
print("run time is :",end-start)
return res
return swapper
@log
def now():
print("function now:",now.__name__)
print("2017-4-10")
now()
result:
function swapper: now
function now: swapper
2017-4-10
run time is : 1.679444439779321e-05
加入functools.warps(func)
import functools
import time
def log(func):
@functools.wraps(func)
def swapper(*args,**kwargs):
print("function swapper:",func.__name__)
start=time.clock()
res=func(*args,**kwargs)
end=time.clock()
print("run time is :",end-start)
return res
return swapper
@log
def now():
print("function now:",now.__name__)
print("2017-4-10")
now()
result:
function swapper: now
function now: now
2017-4-10
run time is : 1.77274690865595e-05
@functools.wraps(func)也是一个装饰器,作用是,把原始函数的__name__等属性复制到 它所修饰的正下方的函数wrapper()中。除了函数名,还有__module__、__doc__等属性。
如何解除装饰器
from functools import wraps
import time
def log(func):
@wraps(func)
def swapper(*args,**kwargs):
print("function swapper:",func.__name__)
start=time.clock()
res=func(*args,**kwargs)
end=time.clock()
print("run time is :",end-start)
return res
return swapper
@log
def now():
print("function now:",now.__name__)
print("2017-4-10")
now()
print("-----")
now.__wrapped__()
执行结果:
function swapper: now
function now: now
2017-4-10
run time is : 3.032331653110589e-05
-----
function now: now
2017-4-10
若装饰器是通过@wraps来实现的,就可以通过__wrapper__属性来访问原始函数。
最后要说的是,并不是所有的装饰器都使用了 @wraps ,因此这里的方案并不全部适用。特别的,内置的装饰器 @staticmethod 和 @classmethod 就没有遵循这个约定 (它们把原始函数存储在属性 func 中)。