由于要入坑机器学习和数据挖掘,有必要先好好钻研一下python,由于之前有那么一点点py的基础,所以这里就不想从零开始学习了,我会不定期地发一写学习过程中遇到的python有趣特性。今天首先来说一下python里的装饰器,之前我发过一篇专门讲java装饰器模式的文章,所以这里先详细谈一下两种装饰器的区别和联系:
相同点:
- 仅看功能的话,python里的装饰器和java的装饰器模式都是为原目标添加新的功能。
不同点:
- python里的装饰器可以作为特性使用(类似java里的注解),而java中的装饰器只是一种设计模式。
- python里的装饰器实际上是其函数式编程特性的一种,所以这个装饰器是一个函数,它装饰的也一个函数。而对java来说,他没有外部方法的概念,对象是一等公民,所以这个装饰器是一个类,它装饰的也是一个类(但是最后也会落实到类的某个方法上)。
实际区别中的第二点正好也是python和java中闭包的区别,一个由函数层面实现,一个由类的层面实现。当然,这是我根据自己的理解总结的,也不一定准确,大家随便看看就行了。我们还是直入主题,来讲讲python里装饰器的用法吧。至于装饰器是干什么用的,可以参考我之前讲java装饰器模式的博客,这里就不再赘述了。
讲之前我先啰嗦几句,从函数式编程的支持来说,感觉python和scala还是做得比较灵活,两者都在面向对象的基础上添加了大量的函数式编程特性。当然scala其实可以另当别论了,因为它本身就是一门函数式编程语言。但是java一直以来都跳不出完全面向对象这个桎梏,最近一些版本里也有改变,比如java8里加入了lamda表达式,也算是向函数式编程靠近吧。但是我想很多人还是希望java能在以后添加更多的函数式编程的特性吧,真的太方便了。当然如果你是学C++的,就可以忽视这些讨论问题了,哪里需要什么函数式编程,指针可以搞定一切。。。
开始,首先定义一个被装饰的函数:
>>> def paint():
... print("I've been painted into black!")
...
我们知道在java中实现装饰,主要是用到了接口和多态。到了python主要用到了高阶函数(可以接受函数作为参数和返回值)的特性来实现的,我们接着定义如下的嵌套函数:
>>> def decorator(func):
... def decorated(*args, **kw):
... print('Put the picture on the wall!')
... return func(*args, **kw)
... return decorated
...
这个函数仔细看的话涉及到的东西还挺多的,但是我们就先撇开这些分析。最外层的函数decorator实际上就是装饰器,它负责接受一个并加工一个函数,在其内部的嵌套函数decorated里就是在具体给之前的函数添加新的功能,最后再将它返回,也就是原来的func函数“升级”成了decorated函数。这里真的是不得不感叹python的灵活简洁。直接通过 return func(*args, **kw)这句话解决了装饰器给原函数添加参数的问题。要是在java里就太麻烦了,先得在装饰器类的内部添加相应的参数字段,然后再通过构造方法初始化参数字段,最后在接口方法里使用这些要添加的参数。
总之就是这么简单,一个装饰器就做好了,怎么用?如下:
>>> @decorator
... def paint():
... print("I've been painted into black")
直接通过@标记引入对应的装饰器,然后在下面定义你的函数就行了。执行被装饰函数结果如下:
>>> paint()
Put the picture on the wall!
I've been painted into black
装饰效果成功达成!!!这种方式时使用内置字段来使用装饰器的,事实上,我们还可以用更复古的方法去使用装饰器,就是像java中那样,将被装饰目标作为对象“注入”到装饰器里:
>>> paint = decorator(paint)
这应该好理解吧,现在paint这个引用指向的是被“装饰过”函数的地址,但是原来那个函数实际上还是在内存中的。还没看python的垃圾回收机制,所以这种情况下的回收我暂时也不太懂,不知道是不是像java里一样发现不在引用树的话直接清理。
>>> paint()
Put the picture on the wall!
I've been painted into black
前面已经说过了可以使用return func(*args, **kw)这样的方法来直接给被装饰函数添加参数,那么如果有时候如果装饰器本身也需要参数呢?比如这里我的例子是往墙上挂东西,我怎么知道挂什么东西呢?接着我们来测试一下在装饰器里传递参数:
>>> def out_deco(what):
... def in_deco(func):
... def decorated(*args, **kw):
... print('Put the %s on the wall!' % what)
... return func(*args, **kw)
... return decorated
... return in_deco
...
我们来用一下,注意这里要@最外层的装饰器:
>>> @out_deco('picture')
... def paint():
... print("I've been painted into black!")
...
它对应的复古操作如下:
>>> now = out_deco('picture')(paint)
这里可能会有人问:为什么只是@了最外层的装饰器,当前函数最多传递给外层装饰器里面的装饰器怎么获得的参数?这里实际上还是用到了闭包的特性。内层函数直接获取外层函数里的参数。
最后来说一个问题吧,我们知道装饰器里返回的是一个函数的引用,那么也就是说接受这个返回值的话也就接受了这个返回函数的一切。聪明的小伙伴可能就不干了,因为这就说明我现在拿到的函数里的__name__域也变成新的函数名了呀,这有点驴头不对马尾吧。解决方法有两种:你可以使用wrapper.__name__ = func.__name__这样的代码,也可以使用Python内置的functools.wraps来实现(推荐),我们现在按照后者来重写一下以上代码:
import functools
def out_deco(what):
def in_deco(func):
@functools.wraps(func)
def decorated(*args, **kw):
print('Put the %s on the wall!' % what)
return func(*args, **kw)
return decorated
return in_deco
注意这里的wraps()函数的使用只能在接收到func参数之后,然后我们运行一下:
@out_deco('picture')
def paint():
print("I've been painted into black!")
print(paint.__name__)
paint()
----------------------------------
Put the picture on the wall!
I've been painted into black!
paint
好了,感觉自己写了好多,累哭了。。。看完python的装饰器是不是感觉其实装饰器也就这样嘛,和java装饰器一样都需要给装饰器注入一个目标并给其添加新功能,只不过python注入的是函数返回的是升级函数,java注入的是对象返回的是升级对象。