很开心的最近终于可以打代码了,之前一直焦虑自己只是会用python,但是很多东西还不知道,或者查了又忘了,其实只是有时间多打代码,慢慢地就会熟悉了,当然还需要的自己想要提高的心,不满足于单纯的解决问题,而是要尽可能完美而优雅的解决问题哈哈
最近想要帮师姐写一个程序对大量的excel数据做大量的处理,需要评估性能,而想起来之前看过别人的代码有@timer用来计时,当时没细看,这回就好好理解了一下这是啥操作,明白了是python的一个工具,装饰器,查了不少资料,算是懂了,之后还是多用吧,先记录一下 :)
装饰器
python的一个功能,其实不需要多想,就顾名思义,装饰器,用来装饰作用的,就好比在CSGO中,同样是枪,有的枪械饰品使得枪上带有一个计数器,能够自动记录下这把枪击杀的人数,本质上功能不会改变,只不过在完成基本功能的同时提供了额外的特性,使得更好地完成任务。
How to use ?
下面就是一个简单的例子,通过提前定义了timer这个计数器,使得在执行被装饰函数同时,记录并输出了运行时间,一方面使得代码的效率更高了(回想起本科院长说过的“复用决定效率”,真的越来越有体会,另一方面,将这些普遍的逻辑抽离出具体的对象,使得编写的时候逻辑更加清晰
下面是简单的示例:
1 | import time |
需要注意的是:
函数也是对象,可以被装饰;类也可以被装饰;
更全面的可以查看,知乎上刘志军先生的回答 https://www.zhihu.com/question/26930016/answer/99243411
非常全面而通俗地介绍了装饰器的用法
A Example
斐波那契数列,大一学C递归的时候就知道的例子,后来做OJ的时候也遇到过,不过那时就提到了,因为计算的时候需要大量的递归计算,如果每次计算的时候都需要计算全部的值会很慢,为了能在规定时间内通过,需要先开一个数组,先查表,如果不在再计算,就很快了,而可以通过functools中自带的一个lru_cache装饰器来帮助我们快速而简单的做到,而且他还不止这样哦,因为在函数内开个数组,函数结束也就没了(不许提全局变量这个憨憨的事儿啊,而这个装饰器是一直伴随着这个函数的,也就是说每次调用该函数,都会现在开出的cache空间中进行查找是否有过类似的输入,直接抛出输出,酷!
一般情况下:
1 | def fib(n): |
而使用了lru
1 |
|
超酷的!
不放心,我们再看看结果如何,同时还可以通过调用cache_info查看到在cache中命中的次数
1 | noffib == noffib_cache |
需要提醒的是,cache装饰器其实就是开辟了一块空间,用来存储输入和输出对应的记录,而lru_cache,用的lru就是操作系统中经常提到的最近最少使用算法,然后functool里的lru_cache有两个参数,一个cache空间的大小,一个是对输入类型严格,比如3和3.0
以上参考的是:知乎上的一篇文章,https://zhuanlan.zhihu.com/p/26151166
这篇也讲得简单:https://www.cnblogs.com/cuiyubo/p/8375859.html
Some powerful functools
所以,我们除了可以自己写一些装饰器以外,还可以调用已有的装饰器来帮助我们解决问题!
functools 模块中主要包含了一些函数装饰器和便捷的功能函数。在 Python 的交互式解释器中先导入 functools 模块,然后输入 [e for e in dir(functools) if not e.startswith(‘_’)] 命令,即可看到该模块所包含的全部属性和函数:
- functools.cmp_to_key(func):将老式的比较函数(func)转换为关键字函数(key function)。在 Python 3 中比较大小、排序都是基于关键字函数的,Python 3 不支持老式的比较函数。
- @functools.lru_cache(maxsize=128, typed=False):该函数装饰器使用 LRU(最近最少使用)缓存算法来缓存相对耗时的函数结果,避免传入相同的参数重复计算。同时,缓存并不会无限增长,不用的缓存会被释放。其中 maxsize 参数用于设置缓存占用的最大字节数,typed 参数用于设置将不同类型的缓存结果分开存放。
- @functools.totalordering:这个类装饰器(作用类似于函数装饰器,只是它用于修饰类)用于为类自动生成比较方法。通常来说,开发者只要提供 lt()、le()、gt()、ge() 其中之一(最好能提供 _eq() 方法),@functools.total_ordering装饰器就会为该类生成剩下的比较方法。
- functools.partial(func, args, *keywords):该函数用于为 func 函数的部分参数指定参数值,从而得到一个转换后的函数,程序以后调用转换后的函数时,就可以少传入那些己指定值的参数。
- functools.partialmethod(func, args, *keywords):该函数与上一个函数的含义完全相同,只不过该函数用于为类中的方法设置参数值。
- functools.reduce(function, iterable[, initializer]):将初始值(默认为 0,可由 initializer 参数指定)、迭代器的当前元素传入 function 函数,将计算出来的函数结果作为下一次计算的初始值、迭代器的下一个元素再次调用 function 函数……依此类推,直到迭代器的最后一个元素。
- @functools.singledispatch:该函数装饰器用于实现函数对多个类型进行重载。比如同样的函数名称,为不同的参数类型提供不同的功能实现。该函数的本质就是根据参数类型的变换,将函数转向调用不同的函数。
- functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES):对 wrapper 函数进行包装,使之看上去就像 wrapped(被包装)函数。
- @functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES):该函数装饰器用于修饰包装函数,使包装函数看上去就像 wrapped 函数。
通过介绍不难发现,functools.update_wrapper 和 @functools.wraps 的功能是一样的,只不过前者是函数,因此需要把包装函数作为第一个参数传入;而后者是函数装饰器,因此使用该函数装饰器修饰包装函数即可,无须将包装函数作为第一个参数传入。
python中functools.singledispatch的使用 https://www.jianshu.com/p/33e1db06f2d5
以上以及更具体的参考来源:http://c.biancheng.net/view/2443.html Python functools模块完全攻略(看了无师自通)
权威的参考查看python的手册:https://docs.python.org/2/library/functools.html
一点思考
多琢磨琢磨吧,是件好事情——告诫自己
继承和装饰器的区别?
从概念上,继承是对父类的扩展,是子类个性化的体现,而装饰器是在不改动自身情况下,根据各自的共性添加上的功能,体现的是大家共同的需求;
从实现上,继承父类后我们可以根据实际情况重写,而被装饰的对象是无法对装饰器产生改动的,只能被动地接受一切安排(最多在装饰器上带入参数,也就是很关键一点,继承后我是还可以在类内调用父类中的变量和函数的,而被装饰对象是无法主动访问调用装饰器里的东西的
其实,我更愿意把装饰器看做是一个简单的继承
他轻轻地来,轻轻地走,不带走一片云彩,却留下人人都盼望的晚霞