注册 登录
  • 欢迎访问"运维那点事",推荐使用Google浏览器访问,可以扫码关注本站的"微信公众号"。
  • 如果您觉得本站对你有帮助,那么可以扫码捐助以帮助本站更好地发展。

Python装饰器

Python编程 彭东稳 322次浏览 已收录 0个评论

一、装饰器

装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。

由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。先看看一些实例, 然后再来分析下原理。假设我们有如下的基本函数:

把函数赋值给一个变量:

函数对象有一个 __name__ 属性,可以拿到函数的名字:

现在,假设我们要增强rge()函数的功能,比如,统计函数的执行时间,但又不希望修改rge()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

本质上,decorator就是一个返回函数的高阶函数,接受一个函数作为参数,并且返回一个函数。所以,我们要定义一个能打印执行时间的decorator,可以定义如下:

timeit()函数的参数定义是(*args, **kw),因此,timeit()函数可以接受任意参数的调用。

执行效果如下:

这种实现看上去还可以,但是每次调用的是decorator,还要把函数作为一个参数传入。这样需要修改调用的地方,使用起来就不方便了。重新定义一下装饰器:

执行效果如下:

观察上面的timeit,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:

效果等效于上面的执行方法。

另外要说明的一点就是,一个装饰器在装饰一个函数或者类的时候就会进行实例化,然后返回函数。而函数只有在被装饰的函数或类做实例化时才会调用。如下测试,在要返回的函数前面输出一个字符:

然后同样去装饰一个函数,我们看一下被装饰的函数创建完成时, print("init") 会不会输出:

可以看出创建完被装饰的函数之后,timeit中定义的 print("init") 执行了,而wrap函数被返回了。当 rge() 函数被执行时才会执行 wrap() 函数。

二、带参数的装饰器

如果装饰器本身需要传入参数,那就需要编写一个返回装饰器的高阶函数。写出来会更复杂。比如,要自定义log的文本:

这个3层嵌套的decorator用法如下:

执行结果如下:

和两层嵌套的decorator相比,3层嵌套的效果是这样的:

我们来剖析上面的语句,首先执行log(‘execute’),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。

以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有 __name__ 等属性,但你去看经过decorator装饰之后的函数,它们的 __name__ 已经从原来的’now’变成了’wrapper’:

因为返回的那个wrapper()函数名字就是’wrapper’,所以,需要把原始函数的 __name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

关于保持函数签名,不需要编写 wrapper.__name__ = fun.__name__这样的代码,Python内置的functools.wraps就是干这个事的(functools提供了两个api,一个是update_wrapper,一个是wrap装饰器函数,但是wrap装饰器函数也是调用了update_wrapper。),所以,一个完整的decorator的写法如下:

或者针对带参数的装饰器:

其中 from functools import wraps是导入解释器内置的wraps模块,模块的概念稍候讲解。现在,只需记住在定义wrapper()的前面加上 @wraps()即可。这个装饰器就是帮我们保持函数签名的。当我们在装饰器中加了这个 @wraps()装饰器之后,再执行 __name__时就恢复正常了。

三、装饰器应用

缓存

写一个函数装饰器,用来缓存函数的值。

Python 3内置functools提供了一个lru_cache装饰器,就是用来提供缓存功能的,只不过lru_cache更加高级,支持lru算法,可设置内存最大缓存条目,当达到上限后就触发lru算法,把最近最少使用的kv删除。

如上我们设置最大缓存条目为1,然后装饰long_time_fun函数,就是让这个函数睡眠。如果我们设置睡眠时间为2秒,那么第一次执行应该会等待2秒钟,同时@lru_cache会把key缓存到内存中,所以第二次执行就非常快了,不需要调用long_time_fun函数了。

第一次执行等待2秒,后面多次执行时间都会很快。另外,我们设置了最大缓存条目为1,所以你可以再调用long_time_fun函数,给3秒睡眠,然后去验证看看前一个2秒的key是否失效了。

监控

完结。。。


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (0)or分享 (0)
关于作者:

您必须 登录 才能发表评论!