python装饰器详解 - 夏晓旭 - 博客园 (cnblogs.com)
两个参考文章
闭包
在函数中嵌套一个函数,并且引用外部函数的变量就是闭包,有点像C语言里面的函数调用
def outer(x):
def inner(y):
return x + y
return inner
print(outer(6)(5))
在上面的outer函数中,定于了一个inner函数,并且inner函数又引用了外部函数outer的变量x,这就是一个闭包。
装饰器
定义
python装饰器就是用于拓展原来函数功能的一种函数,这个函数的特殊之处在于它的返回值也是一个函数,使用python装饰器的好处就是在不用更改原函数的代码前提下给函数增加新的功能。
装饰器的作用其实就是增加函数的功能,减少重复代码
#encoding=utf-8
import time
#定义装饰器
def log(func):
def wrapper(*args,**kw):
print("call func is %s" %func.__name__)
return func(*args,**kw)
return wrapper
@log
def now():
now = time.strftime("%Y-%m-%d %H-%M-%S")
print("current time is %s" %now)
now()
就比如上面的代码,先假设没有log函数,现在的now就只是一个输出时间的函数,但我们像给他添加一些功能,但又不想改变now()的定义,又或者说,希望添加的功能其实本来就可以作为一个函数存在,不用每次都写一遍
那么就可以使用装饰器,Python2.4以后,支持使用标识符@将装饰器应用到函数上,只需要在函数的定义前加上@和装饰器的名称即可
在这里log函数,它是一个装饰器,可以接受一个函数作为参数,并返回一个函数。调用now()函数时,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志
把@log放在now()函数的定义处,相当于执行了now=log(now),log是一个装饰器,返回一个函数,所以原本的now()函数还存在,但now变量指向了新的函数,于是调用now()将执行新函数,执行log()函数返回的wrapper()函数。
wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。正因为有了(*args, **kw)这样的参数格式,这意味着装饰器能够接受拥有任何签名的函数作为自己的被装饰方法(元祖形式,字典形式),同时能够用传递给它的参数(函数)对被装饰的方法进行调用。这样就能处理参数个数不同的函数了。
在now()函数中添加延时
#encoding=utf-8
import time
#定义装饰器
def log(func):
def wrapper(*args,**kw):
print("call func is %s" %func.__name__)
start_time=time.time()
func(*args,**kw)
print("elapse time:",time.time()-start_time)
return ""
return wrapper
@log#相当于now=log(now)
def now():
now = time.strftime("%Y-%m-%d %H-%M-%S")
print("current time is %s" %now)
time.sleep(3)
now()#相当于log(now)(),即wapper()
从结果可以看到,在打印第二行之后,程序等待了3秒后打印了第三行,即在执行wapper()函数时,执行now()函数时等了3秒,然后接着往后执行的打印elapse time部分
分类
装饰器分为无参数decorator和有参数decorator
无参数decorator:生成一个新的装饰器函数
有参数decorator:装饰函数先处理参数,再生成一个新的装饰器函数,然后对函数进行装饰。
对带参数的函数进行装饰
#encoding=utf-8
"""对带参数的函数进行装饰,内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象"""
def deco(func):
def _deco(a,b):
print("before myfunc() called.")
ret=func(a,b)
print(" after myfunc() called. result: %s" %ret)
return ret
return _deco
@deco#等价于myfunc=deco(myfunc)
def myfunc(a,b):
print(" myfunc(%s,%s) called." %(a,b))
return a+b
myfunc(1,2)
myfunc(3,4)
程序运行步骤:
1.因为添加了装饰器,myfunc=deco(myfunc),所以这里myfunc(1,2),其实就变成了deco(myfunc(1,2))
2.那么myfunc(1,2)其实就是deco()函数的参数了
3.现在就可以按顺序执行了
输出before.........
执行ret=func(a,b) #也就是回到myfunc函数,输出myfunc....
输出after........
对参数数量不确定的函数进行装饰
#encoding=utf-8
"""对参数数量不确定的函数进行装饰,
参数用(*args,**kwargs),自动适应变化参数和命名参数"""
def deco(func):
def _deco(*arg,**kw):
print ("before myfunc() called.")
ret=func(*arg,**kw)
print (" after myfunc() called.result: %s" %ret)
return ret
return _deco
@deco#等价于myfunc=deco(myfunc)
def myfunc(a,b,c,d):
print (" myfunc(%s,%s) called." %(a,b))
return a+b
print myfunc(1,2,3,4)#等价于deco(myfunc)(1,2,3,4)
print myfunc(3,4,1,2)#等价于deco(myfunc)(3,4,1,2)
装饰器带参数
def deco(arg):
def _deco(func):
def __deco():
print("before %s called [%s]."%(func.__name__,arg))
func()
print(" after %s called [%s]."%(func.__name__,arg))
return __deco
return _deco
@deco("mymodule")#等价于myfunc=deco("mymodule")(myfunc)
def myfunc():
print("myfunc() called.")
@deco("module2")#等价于myfunc=deco("module2")(myfunc)
def myfunc2():
print("myfunc2() called.")
myfunc()
myfunc2()
@deco("mymodule")
def myfunc():
对于这两行,不管@deco()里面有没有参数,它的意思都是把myfunc函数对象指向一个新函数,这个新函数中调用了原来的myfunc,即myfunc应该指向deco(myfunc),只不过这里特殊的地方在于,deco函数本身传了一个参数arg,即"mymodule",然后在它里面的函数_deco()中,把myfunc作为参数传进去了,之后在_deco()里面的__deco()函数中做了调用,并且返回了__deco,之后又把_deco返回,最终myfunc这个函数对象指向的是_deco这个函数对象以及它所需要的参数(闭包);
myfunc函数对象指向的是deco()这个函数把mymodule和myfunc做为参数传进去后返回的一个函数对象(_deco)以及mymodule和myfunc这两个参数
那么myfunc()就是返回的函数对象的执行,即整个deco函数的执行,执行过程中调用了原来的myfunc函数
类装饰器
class logging(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("[DEBUG]: enter {}()".format(self.func.__name__))
return self.func(*args, **kwargs)
@logging
def hello(a, b, c):
print(a, b, c)
hello("hello,","good","morning")
前面说到的都是函数作为装饰器,类也可以作为装饰器,用法与函数装饰器并没有太大区别,实质是使用了类方法中的call魔法方法来实现类的直接调用。同样的类装饰器也可以带参数
python内置装饰器
Python中内置的装饰器有有三个:
staticmethod:定义实例方法为静态方法
classmethod:定义实例方法为类方法
property:对类属性的操作
注意事项
同时对一个函数使用多个不同的装饰器进行装饰时,最下面的就是最底层的函数
代码示例:
@A
@B
@C
def f():
pass
等价于:
f = A(B(C(f)))