最近很多人问我关于Python装饰器的问题,我把它总结成blog方便他人和自己。
借鉴了这篇关于装饰器的博文,http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html。同时加入了自己的补充和心得。
我用Python IDE来调试,用了Python3.4.2。输出用>>>表示。
第一步:编写一个最简单的函数
#!/usr/bin/env python
def myfunc():
print('myfunc() called')
myfunc()
>>>myfunc() called
第二步:使用装饰函数在函数执行前后分别附加额外功能
#!/usr/bin/env python
def deco(func):
print('before myfunc() called')
func()
print('after myfunc() called')
return func
def myfunc():
print('myfunc() called')
deco(myfunc)
>>>before myfunc() called
>>>myfunc() called
>>>after myfunc() called
如果调用函数改为:
myfunc = deco(myfunc)
myfunc()
>>>before myfunc() called
>>>myfunc() called
>>>after myfunc() called
>>>myfunc() called <====myfunc()的输出
如果调用函数改为:
myfunc = deco(myfunc)
>>>before myfunc() called
>>>myfunc() called
>>>after myfunc() called
第三步:使用棉花糖@语法来装饰函数
#!/usr/bin/env python
def deco(func):
print('before myfunc() called')
func()
print('after myfunc() called')
return func
@deco
def myfunc():
print('myfunc() called')
myfunc()
>>>before myfunc() called
>>>myfunc() called
>>>after myfunc() called
>>>myfunc() called
解析:调用myfunc(),实际上等于 deco(myfunc)()
如果改为:
myfunc
>>>before myfunc() called
>>>myfunc() called
>>>after myfunc() called
解析:调用myfunc,实际上等于 deco(myfunc)
第四步:使用内嵌包装函数来确保每次新函数都被调用
#!/usr/bin/env python
def deco(func):
def wrapper():
print('before myfunc() called')
func()
print('after myfunc() called')
return wrapper
@deco
def myfunc():
print('myfunc() called')
<pre name="code" class="python">myfunc()
>>>before myfunc() called
>>>myfunc() called
>>>after myfunc() called
myfunc()相当于执行包装函数wrapper().
如果调用改为
myfunc
>>>
没有输出,因为这样只是得到了包装函数wrapper的地址,并没有真正去运行它。
第五步:对带固定参数的函数进行装饰
#!/usr/bin/env python
def deco(func):
def wrapper(a,b): ##wrapper的入参必须和func一致
print('before myfunc() called')
func(a,b)
print('after myfunc() called')
return wrapper
@deco
def myfunc(a,b):
ret = a+b
print('{}+{}={}'.format(a,b,ret))
print('myfunc() called')
myfunc(1,2)
>>>before myfunc() called
>>>1+2=3
>>>myfunc() called
>>>after myfunc() called
包装函数wrapper的入参格式,数量必须和func函数一致,因为wrapper就是相当于调用装饰后的func函数。
第六步:对可变入参的函数进行装饰
#!/usr/bin/env python
def deco(func):
def wrapper(*args,**kwargs):
print('before myfunc() called')
func(*args,**kwargs)
print('after myfunc() called')
return wrapper
@deco
def myfunc(a,b,c):
ret = a+b+c
print('{}+{}+{}={}'.format(a,b,c,ret))
print('myfunc() called')
myfunc(1,2,3)
类似于第五步,只是用*args表示可变数量,**kwargs表示key-value形式的参数数量可变
第七步:让装饰器带参数
#!/usr/bin/env python
is_debug = True
def deco(arg):
def midwrapper(func):
def innerwrapper(*args,**kwargs):
print('before myfunc() called')
func(*args,**kwargs)
print('after myfunc() called')
return innerwrapper
return midwrapper
@deco(is_debug)
def myfunc(a,b,c):
ret = a+b+c
if is_debug:
print('{}+{}+{}={}'.format(a,b,c,ret))
print('myfunc() called')
myfunc(1,2,3)
>>>before myfunc() called
>>>1+2+3=6
>>>myfunc() called
>>>after myfunc() called
通过让装饰器带参数is_debug控制log的输出.
当is_debug = False时,输出为myfunc里面有一段代码没有log。
>>>before myfunc() called
>>>myfunc() called
>>>after myfunc() called
第八步:让装饰器带类参数
#!/usr/bin/env python
class locker:
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 midwrapper(func):
def innerwrapper(*args,**kwargs):
print("before {} called {}.".format(func.__name__,cls.__name__))
cls.acquire()
try:
func(*args,**kwargs)
finally:
cls.release()
return innerwrapper
return midwrapper
@deco(locker)
def myfunc(*args,**kwargs):
print('myfunc() called')
myfunc()
>>>before myfunc called locker.
>>>locker.acquire() called
>>>myfunc() called
>>>locker.release() called
解析:
- deco(locker) ==>midwrapper
- myfunc(*args,**kwargs) ==> midwrapper(myfunc) ==>innerwrapper(*args,**kwargs)
- myfunc() ==> innerwrapper()
#!/usr/bin/env python
class mylocker:
def __init__(self):
print('mylocker.__init__() called.')
@staticmethod
def acquire():
print('mylocker.acquire() called.')
@staticmethod
def unlock():
print('mylokcer.unlock() called.')
class lockerex(mylocker):
@staticmethod
def acquire():
print('lockerex.acquire() called.')
@staticmethod
def unlock():
print('lockerex.unlock() called.')
def lockhelper(cls):
def midwrapper(func):
def innerwrapper(*args, **kwargs):
print('before {} called.'.format(func.__name__))
cls.acquire()
try:
func(*args,**kwargs)
finally:
cls.unlock()
return innerwrapper
return midwrapper
class example:
@lockhelper(mylocker)
def myfunc(self):
print('myfunc() called')
@lockhelper(mylocker)
@lockhelper(lockerex)
def myfunc2(self,a,b):
print('{}+{}={}'.format(a,b,a+b))
print('myfunc2() called')
return a+b
if __name__ == '__main__':
a = example()
a.myfunc()
print('=='*15)
a.myfunc2(a=1,b=2)
>>>before myfunc called.
>>>mylocker.acquire() called.
>>>myfunc() called
>>>mylokcer.unlock() called.
>>>==============================
>>>before innerwrapper called.
>>>mylocker.acquire() called.
>>>before myfunc2 called.
>>>lockerex.acquire() called.
>>>1+2=3
>>>myfunc2() called
>>>lockerex.unlock() called.
>>>mylokcer.unlock() called.
看到多个装饰器有些头晕了,让我们慢慢一层一层的剥离就很清晰了。分析起来比较晦涩,我需要先把函数调用顺序学习一下,再来理顺这里的调用关系和返回值得
同时,我将code稍微改变了一下,运行结果也大大超出了我的预期,看来我需要好好再深入研究一下。
#!/usr/bin/env python
class mylocker:
def __init__(self):
print('mylocker.__init__() called.')
@staticmethod
def acquire():
print('mylocker.acquire() called.')
@staticmethod
def unlock():
print('mylokcer.unlock() called.')
class lockerex(mylocker):
@staticmethod
def acquire():
print('lockerex.acquire() called.')
@staticmethod
def unlock():
print('lockerex.unlock() called.')
def lockhelper(cls):
def midwrapper(func):
print('before {} called.'.format(func.__name__))
def innerwrapper(*args, **kwargs):
print('before {} called.'.format(func.__name__))
cls.acquire()
try:
func(*args,**kwargs)
finally:
cls.unlock()
return innerwrapper
return midwrapper
class example:
@lockhelper(mylocker)
def my2func(self):
print('myfunc() called')
@lockhelper(mylocker)
@lockhelper(lockerex)
def myfunc2(self,a,b):
print('{}+{}={}'.format(a,b,a+b))
print('myfunc2() called')
return a+b
if __name__ == '__main__':
a = example()
a.myfunc2(a=1,b=2)
这里预留一个疑问,就是如何清晰解析多个装饰器。