Python装饰器详解
2016-05-31 20:12
585 查看
在上一篇文章中我们提到了闭包,也就是将函数作为返回值返回。闭包搞懂了之后,接下来的内容就很简单了。
在定义了许多函数之后,我们希望扩展这些函数的功能,譬如在函数调用前后自动打印日志,但如果是一些通用的功能,修改每一个函数又会显得比较麻烦。最好的方法就是定义一个装饰器,给每个函数增加功能。这种在代码运行期间动态增加函数功能的方式,成为装饰器(Decorator)
这是最普通的增加函数功能的方式了,但如果将它变成通用功能呢?使用装饰器!
这是装饰器最直观的表示了,定义一个装饰器函数,然后将自己的函数传入,输出的新函数即添加了新功能。
本质上来看,装饰器就是一个高阶函数。由于其接收参数为
但是
只需要把
五、
调用完成之后,新的问题又出现了:函数的
python内置的
你看,实际上这是对wrapper函数的一个装饰器。定义完成之后,就不会除问题了。
这种情况就需要更复杂的嵌套,首先,需要传入一个参数,然后返回一个装饰器函数,执行上面类似的工作,也就是说,在外面再嵌套一层就可以了。
最终,使用语法糖的简便写法如下:
这需要对装饰器的运行过程有充分的了解,观察上面的两类装饰器,主要的区别在于:
没有参数的装饰器,最外层接收的是
带参数的装饰器,最外层接收的是参数,而最终返回的函数是
那么,我们就可以通过判定最外层的函数接收的是
通过最外层传入的参数是否
当然,还有另外一种思路,主体按照带参数的装饰器三层嵌套来写,但是最后返回的时候,需要进行判断:如果最初传入的是参数,那么就返回函数;如果最初传入的就是函数,那么就将第二层函数执行后返回。只不过会在内部替换参数的时候有一些局限。
(By XuZhiyuan 2016-05-31 20点 @Hohai Rainy)
在定义了许多函数之后,我们希望扩展这些函数的功能,譬如在函数调用前后自动打印日志,但如果是一些通用的功能,修改每一个函数又会显得比较麻烦。最好的方法就是定义一个装饰器,给每个函数增加功能。这种在代码运行期间动态增加函数功能的方式,成为装饰器(Decorator)
一、初始函数
>>> from datetime import datetime >>> def now(): print(datetime.now()) >>> now() 2016-05-31 17:04:40.946448
二、增加功能
>>> def now(): print('run %s().' % now.__name__) print(datetime.now()) print('run %s() finished.' % now.__name__) >>> now() run now(). 2016-05-31 17:13:15.216500 run now() finished.
这是最普通的增加函数功能的方式了,但如果将它变成通用功能呢?使用装饰器!
三、装饰器
>>> def decorator(fun): def wrapper(*args,**kw): print('run %s().' % fun.__name__) outcome = fun(*args,**kw) print('run %s() finished.' % fun.__name__) return outcome return wrapper >>> now = decorator(now) >>> now() run now(). 2016-05-31 17:15:08.001145 run now() finished.
这是装饰器最直观的表示了,定义一个装饰器函数,然后将自己的函数传入,输出的新函数即添加了新功能。
本质上来看,装饰器就是一个高阶函数。由于其接收参数为
(*args,**kw),所以能够接受任何形式的调用。在wrapper函数内,调用传入的
fun()函数之外,就可以定义新的功能。
但是
now = decorator(now)这样的方式似乎很麻烦,所以Python提供了装饰器的语法糖@,使得能够简便地使用装饰器。
四、语法糖
语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。>>> def decorator(fun): def wrapper(*args,**kw): print('run %s().' % fun.__name__) outcome = fun(*args,**kw) print('run %s() finished.' % fun.__name__) return outcome return wrapper >>> @decorator def now(): print(datetime.now()) >>> now() run now(). 2016-05-31 17:10:32.217998 run now() finished.
只需要把
@decorator放在函数定义的顶端,再调用函数即为添加功能的新函数。
五、__name__
问题
>>> now() run now(). 2016-05-31 17:10:32.217998 run now() finished. >>> now.__name__ 'wrapper'
调用完成之后,新的问题又出现了:函数的
__name__属性因为使用装饰器改变了,有些依赖函数签名的代码执行就会出错。
python内置的
functools.wraps能够将函数名称替换回来。一个完整的decorator写法如下:
>>> import functools >>> def decorator(fun): @functools.wraps(fun) def wrapper(*args,**kw): print('run %s().' % fun.__name__) outcome = fun(*args,**kw) print('run %s() finished.' % fun.__name__) return outcome return wrapper
你看,实际上这是对wrapper函数的一个装饰器。定义完成之后,就不会除问题了。
>>> @decorator def now(): print(datetime.now()) >>> now() run now(). 2016-05-31 17:32:59.270645 run now() finished. >>> now.__name__ 'now'
六、带参数的Decorator
好了,现在我们已经知道如何实现一个完整的Decorator了,但是,针对不同的函数,我们希望同一个Decorator根据传入的参数,实现不同的功能,这应该怎么实现?这种情况就需要更复杂的嵌套,首先,需要传入一个参数,然后返回一个装饰器函数,执行上面类似的工作,也就是说,在外面再嵌套一层就可以了。
>>> def decorator(arg): def wrapper(func): @functools.wraps(func) def function(*args,**kw): print('%s %s().' % (arg, func.__name__)) outcome = func(*args,**kw) print('%s %s() finished.' % (arg, func.__name__)) return outcome return function return wrapper
decorator接收参数,返回wrapper函数,而这个wrapper函数是一个装饰器。内部的运行如下:
>>> decorator('execute') <function decorator.<locals>.wrapper at 0x0346B6F0> >>> now = decorator('execute')(now) >>> now() execute now(). 2016-05-31 19:15:46.935403 execute now() finished. >>> now.__name__ 'now'
decorator('execute')返回的是
wrapper函数,然后再传入
now函数,最终返回带有新功能&新参数的函数。
最终,使用语法糖的简便写法如下:
>>> @decorator('execute') def now(): print(datetime.now()) >>> now() execute now(). 2016-05-31 19:11:19.024181 execute now() finished. >>> @decorator('run') def now(): print(datetime.now()) >>> now() run now(). 2016-05-31 19:11:45.361102 run now() finished.
七、终极装饰器
那么,问题来了,我们能不能创建一个装饰器,能够传入参数,也可以不传入参数呢?这需要对装饰器的运行过程有充分的了解,观察上面的两类装饰器,主要的区别在于:
没有参数的装饰器,最外层接收的是
func函数,最终返回的函数是
wrapper(*args,**kw),直接接收
func函数参数
带参数的装饰器,最外层接收的是参数,而最终返回的函数是
wrapper(func),接收
func函数
那么,我们就可以通过判定最外层的函数接收的是
func函数还是参数,来决定是有参数或者是无参数的装饰器,最终决定如何返回。尝试结果如下:
>>> def decorator(arg): if callable(arg): @functools.wraps(arg) def wrapper(*args,**kw): print('run %s().' % arg.__name__) outcome = arg(*args,**kw) print('run %s() finished.' % arg.__name__) return outcome return wrapper else: def wrapper(func): @functools.wraps(func) def function(*args,**kw): print('%s %s().' % (arg,func.__name__)) outcome = func(*args,**kw) print('%s %s() finished.' % (arg,func.__name__)) return outcome return function return wrapper
通过最外层传入的参数是否
callable()判断是函数还是参数,进而采取不一样的措施。
>>> @decorator def now(): print(datetime.now()) >>> now() run now(). 2016-05-31 19:51:33.730241 run now() finished. >>> @decorator('execute') def now(): print(datetime.now()) >>> now() execute now(). 2016-05-31 19:53:09.032116 execute now() finished.
当然,还有另外一种思路,主体按照带参数的装饰器三层嵌套来写,但是最后返回的时候,需要进行判断:如果最初传入的是参数,那么就返回函数;如果最初传入的就是函数,那么就将第二层函数执行后返回。只不过会在内部替换参数的时候有一些局限。
>>> def decorator(arg): text = arg if isinstance(arg,str) else '' def wrapper(func): @functools.wraps(func) def function(*args,**kw): print(text) print('run %s().' % func.__name__) outcome = func(*args,**kw) print('run %s() finished.' % func.__name__) return outcome return function return wrapper if isinstance(arg,str) else wrapper(arg)
总结
到这里,装饰器的各部分内容都已经很清晰了。了解装饰器首先一定要将闭包是将函数和自由变量共同返回这一概念透彻清晰,其次,装饰器的内部实现原理一定要很了解,这样就没问题了。(By XuZhiyuan 2016-05-31 20点 @Hohai Rainy)
相关文章推荐
- Python动态类型的学习---引用的理解
- Python3写爬虫(四)多线程实现数据爬取
- 垃圾邮件过滤器 python简单实现
- 下载并遍历 names.txt 文件,输出长度最长的回文人名。
- 深入理解PHP之匿名函数
- install and upgrade scrapy
- Scrapy的架构介绍
- Centos6 编译安装Python
- 使用Python生成Excel格式的图片
- 让Python文件也可以当bat文件运行
- [Python]推算数独
- Python中zip()函数用法举例
- Python中map()函数浅析
- Python将excel导入到mysql中
- Python在CAM软件Genesis2000中的应用
- 使用Shiboken为C++和Qt库创建Python绑定
- FREEBASIC 编译可被python调用的dll函数示例