理解python装饰器
2017-03-09
理解python装饰器
装饰器(Derorators),正如其字面所描述,在原函数的基础上进行一些额外的操作。接下来我们将从源头出发,探探python的装饰器。
从函数开始
在日常的工作中,我们需要写形形色色的业务函数,如example 1
:
#example 1
def deal_business(arg):
print("Dealing Business in {}".format(arg))
def main():
# in block 1
deal_business("block 1")
# in block 2
deal_business("block 2")
if __name__ == "__main__":
main()
在某一天,你需要去统计在各个block中业务处理时间,比较朴素的方案如example 2
:
#example 2
import time
def deal_business(arg):
print("Dealing Business in {}".format(arg))
def main():
# in block 1
time_start_1 = time.clock()
deal_business("block 1")
time_end_1 = time.clock()
print("time for block 1")
# in block 2
time_start_2 = time.clock()
deal_business("block 2")
time_end_2 = time.clock()
print("time for block 2")
if __name__ == "__main__":
main()
这种方案当然不会有问题,但是他需要在每次调用deal_business
的地方都进行一个统计操作,并不是一个好的选择。那么另外一种思路,在函数里面进行统计,这样就不需要修改调用的代码了。
#example 3
import time
def deal_business(arg):
time_start_1 = time.clock()
print("Dealing Business in {}".format(arg))
time_end_1 = time.clock()
print("time for {}".format(arg))
def main():
# in block 1
deal_business("block 1")
# in block 2
deal_business("block 2")
if __name__ == "__main__":
main()
这种方案当然也是可行的,但是他破坏了函数的结构。另外一方面,对于不同的业务函数,都需要进行对应的修改。更进一步,我们想到去使用一个outer函数,把业务作为参数穿进去来进行计时操作,如example 4
:
#example 4
import time
def record_time(f, arg):
time_start_1 = time.clock()
f(arg)
time_end_1 = time.clock()
print("time for {}".format(arg))
def deal_business(arg):
print("Dealing Business in {}".format(arg))
def main():
# in block 1
#deal_business("block 1")
record_time(deal_business, "block 1")
# in block 2
#deal_business("block 2")
record_time(deal_business, "block 2")
if __name__ == "__main__":
main()
这种方案解决了example 3
中的不足,针对各种函数都能进行调用。但是和example 2
一样,需要修改调用代码。幸运的是,python提供了一个语法糖,他可以让我们不修改业务代码的情况下进行替换,那就是@
。
@
语法
@
语法实际上执行的是一个翻译的动作,以函数作为参数,翻译出函数并进行相关的运行操作。比如说:
@dec2
@dec1
def func(arg1, arg2, ...):
pass
#This is equivalent to:
def func(arg1, arg2, ...):
pass
func = dec2(dec1(func))
他实际上是先进行翻译,再根据翻译的后的函数进行一个函数调用,这类似于C/C++
里面的宏展开。那么在这里最值得注意的一个地方就是,不要在decorator
中返回函数的执行,而是返回函数的对象。如example 5
:
#example 5
def decorator(f):
print("in decorator")
return f()
@decorator
def f():
print("in f")
'''
这个会被翻译成 :
print("in decorator")
f()()
而且更需要注意的是,他会在解释的时候就执行,也就是说在 def f() 这一行执行
'''
既然是即时执行,那么我们怎么去做到延迟执行呢?很好的一个方案是使用闭包(Closure)。
Closure
python中的闭包是指引用了自由变量的函数。所谓自由变量是指作用域包含闭包函数的变量,他们在闭包函数引用后生存期会延长到闭包函数的生存期(实际上有闭包函数的__closure__
属性维护)。因而,对于example 5
我们可以进行这样的变化:
#example 6
def decorator(f):
def inner():
print("in decorator")
f()
return inner
@decorator
def f():
print("in f")
def main():
f()
if __name__ == "__main__":
main()
'''
上例会被翻译成 :
def f():
print("in decorator")
print("in f")
'''
inner
是一个闭包函数,他可以调用上层函数的自由变量f
,并执行。
装饰器,GO
有了@
语法糖和闭包函数的特性,我们可以对之前的example进行改造了。回到example 4
,我们需要做的是把record_time做成一个装饰器。改造后的示例如example 7
:
#example 7
import time
def record_time(f):
def inner(arg):
time_start_1 = time.clock()
f(arg)
time_end_1 = time.clock()
print("time for {}".format(arg))
return inner
@record_time
def deal_business(arg):
print("Dealing Business in {}".format(arg))
def main():
# in block 1
deal_business("block 1")
# in block 2
#deal_business("block 2")
if __name__ == "__main__":
main()
'''
上例会被翻译成 :
def deal_business(arg):
time_start_1 = time.clock()
print("Dealing Business in {}".format(arg))
time_end_1 = time.clock()
print("time for {}".format(arg))
'''
更近一步,针对更加复杂的案例,我们同样可以解决。比如,如果deal_business
的参数定义为def deal_business(*args, **kwargs)
,只需要给inner
同样的参数即可。这些在本次讨论之外,点到为止。
另外一方面,装饰器可以由函数实现,所以他可以可以做到嵌套调用以及参数调用。如example 8
给出了带参数的装饰器以及嵌套使用的装饰器:
# example 8
import time
def nest_description(*nest_args):
print("user info: {}".format(nest_args))
def outer(f):
def inner(arg):
f(arg)
return inner
return outer
def record_time(**deco_args):
def outer(f):
arg_list = [" : ".join([item[0], item[1]]) for item in deco_args.items()]
print("version info: {}".format(arg_list))
def inner(arg):
time_start_1 = time.clock()
f(arg)
time_end_1 = time.clock()
print("time for {}".format(arg))
return inner
return outer
@nest_description("erdao", "master")
@record_time(name = "test", version = "v1")
def deal_business(arg):
print("Dealing Business in {}".format(arg))
def main():
# in block 1
deal_business("block 1")
# in block 2
# deal_business("block 2")
if __name__ == "__main__":
main()
实际上,装饰器还可以由类来实现。作为装饰器存在的类,他有以下几个特征:
- 类需要实现
__init__
和__call__
两个方法 __init__
实现对编译器的解释,__call__
完成对编译器的调用
从example 9
我们可以看到类式装饰器的解译和执行的顺序。
# example 9
class ExampleDecorator(object):
def __init__(self, f):
print("init decorator")
self._func = f
def __call__(self):
print("call")
self._func()
@ExampleDecorator
def func_a():
print("in func a")
@ExampleDecorator
def func_b():
print("in func b")
print("special cut line")
def main():
func_a()
func_b()
if __name__ == "__main__":
main()
'''
输出是 :
init decorator
init decorator
special cut line
call
in func a
call
in func b
'''
也就是说在@
语法糖执行的时候进行__init__
,在调用的时候执行__call__
。更深一步的探入,@
语法实际上执行的是以下操作:
@ExampleDecorator
def f()
# 等同于
f = ExampleDecorator(f).__call__
但是,这种方式是有一个限制的:我们发现不能对类式装饰器进行参数初始化。幸运的是,我们可以利用@
忠实解释的过程,__call__
操作前移,即在@
的时候进行参数调用:
# example 10
class ExampleDecorator(object):
def __init__(self, *args):
print("init decorator")
self._arglist = args
def __call__(self, f):
print("decorator arglist : {}".format(self._arglist))
print("call")
return f
@ExampleDecorator("ver1")
def func_a():
print("in func a")
@ExampleDecorator("ver2")
def func_b():
print("in func b")
print("special cut line")
def main():
func_a()
func_b()
if __name__ == "__main__":
main()
上例中解释为:
f = ExampleDecorator("ver1").__call__(f)
如果有需要对func_a
带上参数的话,可以仿照之前闭包函数的实现方式进行实现。
装饰器用处
说了这么多装饰器的原理是实现,那么装饰器都可以用在什么地方呢?
- 辅助功能——比如在常用的Logging/Debugging中输出函数的调用递归、行数,时间消耗等
- 缓存——进行某些优化
- 隔离——根据一些attr来区别运行
- 类型保障——在函数运行前保障参数类型
最后几个例子
#example 11
'''
实现一个登录系统:检测是否有账户,如果有进行验证,否则抛出异常
'''
def authenticate(func):
def authenticate_and_call(*args, **kwargs):
if not Account.is_authentic(request):
raise Exception('Authentication Failed.')
return func(*args, **kwargs)
return authenticate_and_call
def authorize(role):
def wrapper(func):
def authorize_and_call(*args, **kwargs):
if not current_user.has(role):
raise Exception('Unauthorized Access!')
return func(*args, **kwargs)
return authorize_and_call
return wrapper
@authorize('admin')
@authenticate
def deal_request(request):
print("login in")
#example 12
'''
实现一个单例模式
'''
def singleton(cls):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return getinstance
@singleton
class MyClass(object):
def process(self):
print("in singleton MyClass")
a = MyClass()
print(a)
b = MyClass()
print(b)
#example 13
'''
实现对参数数量的限定
'''
def default_error_func(*args):
print("wrong args")
def accepts(f):
if f.func_code.co_argcount != 2:
return default_error_func
return f
@accepts
def func_a(arg1):
print("in func a")
@accepts
def func_b(arg1, arg2):
print("in func b")
def main():
func_a(1)
func_b(1, "1")
if __name__ == "__main__":
main()