装饰器
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了装饰器相关的知识,希望对你有一定的参考价值。
Python 装饰器(Decorator)
在代码运行期间在不改变原函数定义的基础上,动态给该函数增加功能的方式,称之为装饰器(Decorator)。
装饰器是一个很著名的设计模式,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象(函数)添加额外的功能。
总体来说,装饰器其实也是一个函数,一个用来包装函数的函数,返回一个修改之后的函数对象,将其重新赋值给原来的标识符,并永久丧失对原始函数对象的访问。
装饰器:给函数增加一些通用的功能
有点和用途:
抽离出大量函数中与函数功能本身无关的的雷同代码并继续重用。使用装饰器可以将函数“修饰”为完全不同的行为,可以有效的将业务逻辑正交分解,如用于将权限与身份验证从业务中独立出来。如果一个函数需要一个功能,如果这个功能可以被使用在很多函数上,或是函数并不是自己实现,那可以写个装饰器来实现这些功能。概况的将,装饰器的作用就是为已经存在的对象添加一些额外的功能。在正式进入装饰器讲解前,我们先学一点之前没有涉及到的一些而这里需要用到的一些知识。
变量相关-作用域
#coding=utf-8
outerVar = "this is a global variable"
def test() :
innerVar = "this is a Local variable"
print ("local variables :")
print (locals())
test()
print ("global variables :")
print (globals())
变量相关--变量解析规则
在python的作用域规则里面,创建变量时一定会在当前作用域里创建同样的变量,但访问或修改变量时,会在当前作用域中查找该变量,如果没找到匹配的变量,就会依次向上在闭合作用域中进行查找,所以在函数中直接访问全局变量也是可以的。
变量相关—变量生存空间
变量不仅仅是存在于一个个的命名空间中,它们还都有自己的生存周期,全局变量的生存周期是在整个程序执行期间有效,而局部变量的生存周期只在当前作用域中有效,一旦这个作用域不存在了,比如函数执行退出了,变量的生存周期就结束了。
变量相关—嵌套函数
#coding=utf-8
def outer():
name = "python"
def inner():
print(name)
return inner()
outer()
闭包
#coding=utf-8
def outer():
name = "python"
def inner():
print(name)
return inner
res = outer()
res()
print(res.__closure__)
如果一个函数定义在另一个函数的作用域内,并且引用了外层函数的变量,则该函数称为闭包。
闭包是Python所支持的一种特性,它让在非global scope定义的函数可以引用其外围空间中的变量,这些外围空间中被引用的变量叫做这个函数的环境变量。环境变量和这个非全局函数一起构成了闭包。
上例中的inner()函数就是一个闭包,它本身也是一个函数,而且还可以访问本身之外的变量。
这能够通过查看函数的func_closure属性得出结论,这个属性里面包含封闭作用域里面的值(只会包含被捕捉到的值,比如name,如果在outer里面还定义了其他的值,封闭作用域里面是不会有的)
每次函数outer被调用时,inner函数都会被重新定义,上面每次返回的函数inner结果都一样,因为name没变。如下例所示,我们将函数稍微改动一下,结果就不一样了
#coding=utf-8
def outer(name):
def inner():
print(name)
return inner
res = outer("hhq")
res2 = outer("kkkk")
res()
res2()
装饰器分类
装饰器分为无参数decorator和有参数decorator
无参数decorator:生成一个新的装饰器函数
有参数decorator:装饰函数先处理参数,再生成一个新的装饰器函数,然后对函数进行装饰。
装饰器的具体定义:
1、把要装饰的方法作为输入参数;
2、在函数体内可以进行任意的操作(可以想象其中会有很多应用场景);
3、只要确保最后返回一个可执行的函数即可(可以是原来的输入参数函数,也可以是一个新函数)
装饰器学习九步法
第一步:最简单的函数,准备附加额外功能
#coding=utf-8
def myfunc():
print("myfunc() called")
myfunc()
myfunc()
第二步:使用装饰函数在函数执行前和执行后分别附加额外功能
#coding=utf-8
def deco(func):
print("before myfunc() called")
func()
print("after myfunc() called")
return func
def myfunc():
print("myfunc() called")
myfunc = deco(myfunc)
myfunc()
myfunc()
第三步:
使用语法糖@来装饰函数,相当于“myfunc = deco(myfunc)”但发现新函数只在第一次被调用,且原函数多调用了一次。等价于第二步程序‘‘‘
#coding=utf-8
def deco(func):
print("before myfunc() called")
func()
print("after myfunc() called")
return func
@deco
def myfunc():#执行myfunc = deco(myfunc)
print("myfunc() called")
myfunc()
myfunc()
#注释掉最后两行代码可以看到装饰器函数也会被执行
执行结果分析:
1、
@deco #执行myfunc = deco(myfunc)
def myfunc():
print " myfunc() call
相当于执行myfunc = deco(myfunc),调用了deco(myfunc)函数所以输出结果的前三行内容
2、
myfunc()
myfunc()
这两句执行的是myfunc = deco(myfunc)重新赋值后的myfunc加括号后函数myfunc(),之所以会输出是因为demo(myfunc)返回的是原
来定义的函数myfunc
第四步使用内嵌包装函数来确保每次新函数都被调用
#coding=utf-8
import functools
def deco(func):
@functools.wraps(func)
def _deco():
print("before myfunc() called")
func()
print("after myfunc() called")
return _deco
@deco
def myfunc():#myfunc = deco(myfunc)
print("myfunc called()")
return "ok"
myfunc() #此处执行的是重新赋值后的myfunc,也就是_deco()
print("*******")
myfunc()
第五步 对带参数的函数进行装饰
对带参数的函数进行装饰,内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象‘‘‘
#coding=utf-8
import functools
def deco(func):
@functools.wraps(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
def myfunc(a,b):#myfunc = deco(myfunc)
print("myfunc(%s,%s) called()" %(a,b))
return a + b
myfunc(1,2) #deco(myfunc)(1,2) ==> _deco(1,2)
myfunc(3,4)
等价于以下:
#coding=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
def myfunc(a, b):
print (" myfunc(%s,%s) called." % (a, b))
return a + b
deco(myfunc)(1,2)
print ("**************************")
deco(myfunc)(3, 4)
第六步:对参数数量不确定的函数进行装饰
#coding=utf-8
def deco(func):
def _deco(*args,**kwargs):
print("before %s called" %func.__name__)
ret = func(*args,**kwargs)
print("after %s called" %func.__name__)
return ret
return _deco
@deco
def myfunc(a,b):#等价于myfunc = deco(myfunc) ==> _deco
print("myfunc(%s,%s) called" %(a,b))
return a + b
@deco
def myfunc2(a,b,c)::#等价于myfunc2 = deco(myfunc2) ==> _deco
print("myfunc(%s,%s,%s) called" %(a,b,c))
return a + b + c
print(myfunc(1,2))#deco(myfunc)(1,2) ==》 _deco(1,2)
print("*************88")
print(myfunc2(1,2,3))#deco(myfunc2)(1,2,3) ==》 _deco(1,2,3)
第七步:让装饰器带参数
和上一示例相比在外层多了一层包装。装饰函数名实际上应更有意义些‘
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print(‘%s %s():‘ % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
这个3层嵌套的decorator用法如下:
@log(‘execute‘)
def now():
print(‘2015-3-25‘)
执行结果如下:
>>> now()
execute now():
2015-3-25
和两层嵌套的decorator相比,3层嵌套的效果是这样的:
>>> now = log(‘execute‘)(now)
我们来剖析上面的语句,首先执行log(‘execute‘),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。
以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator(装饰器)装饰之后的函数,它们的__name__已经从原来的‘now‘变成了‘wrapper‘:
>>> now.__name__
‘wrapper‘
因为返回的那个wrapper()函数名字就是‘wrapper‘,所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。
不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print(‘call %s():‘ % func.__name__)
return func(*args, **kw)
return wrapper
或者针对带参数的decorator:
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print(‘%s %s():‘ % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
import functools是导入functools模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()的前面加上@functools.wraps(func)即可。
装饰器带参数,如果装饰器本身需要传入参数,需要编写一个返回装饰器的高阶函数;
#coding=utf-8
Import functools
def deco(arg):
def _deco(func):
@functools.wraps(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")
def myfunc():#等价于myfunc = deco("mymodule")(myfunc) ==> __deco
print("myfunc() called")
@deco("moudule2")#等价于myfunc = deco("moudule2")(myfunc2) ==> __deco
def myfunc2():
print("myfunc2() called")
myfunc() #等价于deco("mymodule")(myfunc)() ==> __deco()
print("******")
myfunc2()#等价于deco("moudule2")(myfunc2)() ==> __deco()
等价于:
#coding=utf-8
import functools
def deco(arg):
def _deco(func):
@functools.wraps(func)
def __deco():
print("before %s called[%s]." %(func.__name__,arg))
func()
print("after %s called[%s]." %(func.__name__,arg))
return __deco
return _deco
def myfunc():
print("myfunc() called")
def myfunc2():
print("myfunc2() called")
deco("moudule2")(myfunc2)()
deco("mymodule")(myfunc)()
第八步:让装饰器带类参数
#coding=utf-8
import functools
class locker(object):
def __init__(self):
print("locker.__init__() should be not called.")
@staticmethod
def acquire():
print("locker.acquire() called.(这是静态方法)")
@staticmethod
def release():
print("locker.release() called.(不需要对象实例)")
def deco(cls):
def _deco(func):
def __deco():
print("before %s called[%s]" %(func.__name__,cls))
cls.acquire()
try:
return func()
finally:
cls.release()
return __deco
return _deco
@deco(locker)
def myfunc():#myfunc = deco(locker)(myfunc) ==> __deco
print("myfunc() called")
myfunc()#__deco()
myfunc()#__deco()
第九步 装饰器带类参数,并分拆公共类到其他py文件中
装饰器带类参数,并分拆公共类到其他py文件中,同时演示了对一个函数应用多个装饰器
#coding=utf-8
import functools
class mylocker(object):
def __init__(self):
print("mylocker.__init__() called.")
@staticmethod
def acquire():
print("mylocker.acquire() called.")
@staticmethod
def unlock():
print("mylocker.unlock() called")
class lockerex(mylocker):
@staticmethod
def acquire():
print("lockerex.acquire() called")
@staticmethod
def unlock():
print("lockerex.unlock() called.")
def lockhelper(cls):
def _deco(func):
def __deco(*args,**kwargs):
print("before %s called." %func.__name__)
cls.acquire()
try:
return func(*args,**kwargs)
finally:
cls.unlock()
return __deco
return _deco
class example(object):
@lockhelper(mylocker)
def myfunc(self):
print("myfunc() called.")
@lockhelper(mylocker)
@lockhelper(lockerex)
def myfunc2(self,a,b):
print("myfunc2() called.")
return a + b
if __name__ == "__main__":
a = example()
a.myfunc()
print("_________")
print(a.myfunc())
print("_________")
print(a.myfunc2(1,2))
print("_________")
print(a.myfunc2(3,4))
装饰器顺序
但同时对一个函数使用多个不同的装饰器进行装饰时,
这个时候装饰器的顺序就很重要了。
代码示例:
@A
@B
@C
def f():
pass
等价于:
f = A(B(C(f)))
以上是关于装饰器的主要内容,如果未能解决你的问题,请参考以下文章