Python装饰器

很开心的最近终于可以打代码了,之前一直焦虑自己只是会用python,但是很多东西还不知道,或者查了又忘了,其实只是有时间多打代码,慢慢地就会熟悉了,当然还需要的自己想要提高的心,不满足于单纯的解决问题,而是要尽可能完美而优雅的解决问题哈哈

最近想要帮师姐写一个程序对大量的excel数据做大量的处理,需要评估性能,而想起来之前看过别人的代码有@timer用来计时,当时没细看,这回就好好理解了一下这是啥操作,明白了是python的一个工具,装饰器,查了不少资料,算是懂了,之后还是多用吧,先记录一下 :)

装饰器

python的一个功能,其实不需要多想,就顾名思义,装饰器,用来装饰作用的,就好比在CSGO中,同样是枪,有的枪械饰品使得枪上带有一个计数器,能够自动记录下这把枪击杀的人数,本质上功能不会改变,只不过在完成基本功能的同时提供了额外的特性,使得更好地完成任务。

How to use ?

下面就是一个简单的例子,通过提前定义了timer这个计数器,使得在执行被装饰函数同时,记录并输出了运行时间,一方面使得代码的效率更高了(回想起本科院长说过的“复用决定效率”,真的越来越有体会,另一方面,将这些普遍的逻辑抽离出具体的对象,使得编写的时候逻辑更加清晰

下面是简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.clock()
func(*args,**kwargs)
end = time.clock()
print("time cost:{}.s".format(end-start))
return wrapper
@timer
def bar():
time.sleep(3)
print("i am bar")

bar()
>>>i am bar
time cost:2.9996414000000002.s

需要注意的是:

函数也是对象,可以被装饰;类也可以被装饰;

更全面的可以查看,知乎上刘志军先生的回答 https://www.zhihu.com/question/26930016/answer/99243411

非常全面而通俗地介绍了装饰器的用法

A Example

斐波那契数列,大一学C递归的时候就知道的例子,后来做OJ的时候也遇到过,不过那时就提到了,因为计算的时候需要大量的递归计算,如果每次计算的时候都需要计算全部的值会很慢,为了能在规定时间内通过,需要先开一个数组,先查表,如果不在再计算,就很快了,而可以通过functools中自带的一个lru_cache装饰器来帮助我们快速而简单的做到,而且他还不止这样哦,因为在函数内开个数组,函数结束也就没了(不许提全局变量这个憨憨的事儿啊,而这个装饰器是一直伴随着这个函数的,也就是说每次调用该函数,都会现在开出的cache空间中进行查找是否有过类似的输入,直接抛出输出,酷!

一般情况下:

1
2
3
4
5
6
7
8
9
10
def fib(n):
if n==1 or n==0:
return 1
else:
return fib(n-2) + fib(n-1)
start=time.time()
noffib = [fib(n) for n in range(40)]
end=time.time()
print('cost:{}'.format(end-start))
>>>cost:61.7529993057251

而使用了lru

1
2
3
4
5
6
7
8
9
10
11
@functools.lru_cache(maxsize=128)
def fib_cache(n):
if n==1 or n==0:
return 1
else:
return fib_cache(n-2) + fib_cache(n-1)
start=time.time()
noffib_cache = [fib_cache(n) for n in range(40)]
end=time.time()
print('cost:{}'.format(end-start))
>>>cost:0.0

超酷的!

不放心,我们再看看结果如何,同时还可以通过调用cache_info查看到在cache中命中的次数

1
2
3
4
noffib == noffib_cache
>>>True
fib_cache.cache_info()
>>>CacheInfo(hits=76, misses=40, maxsize=128, currsize=40)

需要提醒的是,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

一点思考

多琢磨琢磨吧,是件好事情——告诫自己

继承和装饰器的区别?

从概念上,继承是对父类的扩展,是子类个性化的体现,而装饰器是在不改动自身情况下,根据各自的共性添加上的功能,体现的是大家共同的需求;

从实现上,继承父类后我们可以根据实际情况重写,而被装饰的对象是无法对装饰器产生改动的,只能被动地接受一切安排(最多在装饰器上带入参数,也就是很关键一点,继承后我是还可以在类内调用父类中的变量和函数的,而被装饰对象是无法主动访问调用装饰器里的东西的

其实,我更愿意把装饰器看做是一个简单的继承

他轻轻地来,轻轻地走,不带走一片云彩,却留下人人都盼望的晚霞

0%