Python 装饰器初探

Python 装饰器初探

在谈及Python的时候,装饰器一直就是道绕不过去的坎。面试的时候,也经常会被问及装饰器的相关知识。总感觉自己的理解很浅显,不够深刻。是时候做出改变,对Python的装饰器做个全面的了解了。

1. 函数装饰器

直接上代码,看看装饰器到底干了些什么?

from functools import wraps
import timedef time_cost(func):@wraps(func)def f(*args, **kwargs):start_time = time.time()func(*args, **kwargs)end_time = time.time()print(end_time - start_time)return f@time_cost
def test(*args, **kwargs):time.sleep(1.0)if __name__ == "__main__":test()

上面的Python代码,运行后,会给出test函数的执行时间。代码的执行顺序大概如下,首先是将test作为值传递给time_cost函数,返回函数f,然后再调用f,这是带有time_cost装饰器的test函数的大致执行过程。

从中,不难看出,即使不使用装饰器符号,我们利用Python的语言特性,也能达成上述目的。用装饰器符号的好处是简化了代码,增加了代码的可读性。

这是一段非常简单的对函数使用装饰器的Python代码。等等,@wraps(func)是什么鬼?悄悄干了什么哇?

我们稍微修改下上述代码,结果如下:

from functools import wraps
import timedef time_cost(func):def f(*args, **kwargs):start_time = time.time()func(*args, **kwargs)end_time = time.time()print(end_time - start_time)print('hello world')return f@time_cost
def test(*args, **kwargs):time.sleep(1.0)if __name__ == "__main__":print(test.__name__)

发现输出了hello world,同时输出test.__name__,居然变成了f,并不是我们预期的test。根据这样的输出结果,我们不难得出,其实被装饰器time_cost修饰过的函数test本质上已经等同于time_cost(test),此时访问test.__name__实际上访问的是time_cost(test).__name__,得到的当然就是f啦。当我们加上@wraps(func),此时test.__name__变成了test

下面介绍带参数的装饰器,更加难了。在谈论带参数的装饰器之间,首先得引入一个概念,那就”闭包“。如果你以前用过脚本语言,比如JavaScript,那么一定会很熟悉闭包这个概念。下面是一个闭包样例

def add(a):def wrapper(c):return a + creturn wrapperif __name__ == "__main__":add3 = add(3)add9 = add(9)print(add3(4) == 7)print(add9(1) == 10)

从中可以看出,在调用add3的时候,wrapper内部还可以访问到a的值,这就是闭包的作用。理解了闭包,理解带参数的装饰器就容易多了。

from functools import wrapsdef logging(level):def outer_wrapper(func):@wraps(func)def inner_wrapper(*args, **kwargs):print("[{level}]: enter function {func}()".format(level=level,func=func.__name__))return func(*args, **kwargs)return inner_wrapperreturn outer_wrapper@logging(level='WARN')
def show(msg):print('message:{}'.format(msg))if __name__ == "__main__":show('hello world!')

上面给出了一个带参数装饰器的示例。根据我们前面的铺垫,我们不难分析得出,上面的执行过程是logging(level='WARN')->outer_wrapper(show)->inner_wrapper(),所以我们可以理解,在被logging修饰后的show其实就是logging(level='WARN')(show),执行show('hello world!')其实就是在执行logging(level='WARN')(show)()。注意与不带参数的装饰器的区别,带参数的装饰器比不带参数的装饰器多套了一层,对应的装饰器也有了调用。因为在使用装饰器的时候,带了括号,所以装饰器本身多套了一层。被装饰器修饰过的函数在被调用的时候,实际上执行的是装饰器最内层的函数,其余层的在函数被修饰时就已经执行了。

是不是觉得非常自然?对的,我以前对装饰器的理解也就停留在不带参数的装饰器这一深度。

2. 基于类实现的装饰器

依然先上代码

from functools import wraps
import timeclass time_cost:def __init__(self, func):self.func = funcdef __call__(self, *args, **kwargs):start_time = time.time()result = self.func(*args, **kwargs)end_time = time.time()print(end_time - start_time)return result@time_cost
def test(*args, **kwargs):time.sleep(1.0)if __name__ == "__main__":test()

上面的基于类实现的不带参数的装饰器实际上利用的是Python中的可调用对象特性,凡是实现了__call__方法的类的实例是可以被调用的。因此被time_cost修饰过的test函数本质上已经变成了time_cost类的实例了。调用test方法的时候,实际上执行的是__call__方法。

下面介绍稍微复杂一点的基于类实现的带有参数的装饰器。

from functools import wrapsclass logging:def __init__(self, level):self.level = leveldef __call__(self, func):@wraps(func)def wrapper(*args, **kwargs):print("[{level}]: enter function {func}()".format(level=self.level,func=func.__name__))return func(*args, **kwargs)return wrapper@logging(level='WARN')
def show(msg):print('message:{}'.format(msg))if __name__ == "__main__":show('hello world!')

不同于基于类实现的不带参数的装饰器,基于类实现的带参数的装饰器在__call__里面多了一层wrapper。被装饰器修饰的show方法本质上是logging(level='WARN')(show),此时调用show方法,实际上执行的是wrapper方法。

现在看来,其实装饰器也没有很复杂,在实际的项目中用装饰器可以带来很大便利。

转载于:https://www.cnblogs.com/crackpotisback/p/10197698.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/412508.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[css] 解释下 CSS sprites的原理和优缺点分别是什么

[css] 解释下 CSS sprites的原理和优缺点分别是什么 我来说下我的观点 原理: 多张图合并成一张图优点&解决的问题hover效果,如果是多个图片,网络正常的情况下首次会闪烁一下。如果是断网情况下,就没图片了。sprites 就很好的…

《自律100天,穿越人生盲点》读书笔记

大家好,我是烤鸭: 《自律100天,穿越人生盲点》,读书笔记。 第一章 “自律100天”的华丽开启 第一节 “自律100天”的底层逻辑 习惯没办法用金钱换,只能用时间。 训练延迟满足(增强自控、培养耐心、减少短期诱惑…

递推数列

题目描述 给定a0,a1,以及anpa(n-1) qa(n-2)中的p,q。这里n > 2。 求第k个数对10000的模。 输入描述: 输入包括5个整数&#xff1a;a0、a1、p、q、k。 输出描述: 第k个数a(k)对10000的模。 分析 循环求出ak即可 #include <iostream>using namespace std;int main(){in…

[css] 请描述margin边界叠加是什么及解决方案

[css] 请描述margin边界叠加是什么及解决方案 1&#xff0c;使用padding代替&#xff0c;但是父盒子要减去相应的高度 2&#xff0c;使用boder&#xff08;透明&#xff09;代替&#xff08;不推荐&#xff0c;不符合书写规范&#xff0c;如果父盒子子盒子时有颜色的不好处理&…

从线上慢sql看explain关键字

大家好&#xff0c;我是烤鸭&#xff1a; 最近有点忙的头晕&#xff0c;又懒又累&#xff0c;正好线上遇到慢sql的问题&#xff0c;就说下 MySQL Explain 关键字的解析和使用示例。 explain 关键字说明 使用explain关键字可以模拟优化器执行sql查询语句&#xff0c;从而得…

[css] style标签写在body前和body后的区别是什么?

[css] style标签写在body前和body后的区别是什么&#xff1f; 渲染机制的区别&#xff0c;在body前是已经把样式浏览一遍&#xff0c;到了对应标签直接&#xff0c;渲染样式。显示块。 在body后&#xff0c;是浏览器已经把标签浏览了&#xff0c;但基于没有样式&#xff0c;显…

自然语言处理的一些链接

Word2Vec Tutorial - The Skip-Gram ModelVisualizing A Neural Machine Translation Model (Mechanics of Seq2seq Models With Attention) 转载于:https://www.cnblogs.com/linyihai/p/10200351.html

《Java并发编程实践-第一部分》-读书笔记

大家好&#xff0c;我是烤鸭&#xff1a; 《Java并发编程实战-第一部分》-读书笔记。 第一章&#xff1a;介绍 1.1 并发历史&#xff1a; 多个程序在各自的进程中执行&#xff0c;由系统分配资源&#xff0c;如&#xff1a;内存、文件句柄、安全证书。进程间通信方式&#x…

[css] 说说你对css盒子模型的理解

[css] 说说你对css盒子模型的理解 css盒模型由两个盒子组成&#xff0c;外在的控制是否换行的盒子&#xff0c;以及内在的控制元素内容的盒子。比如&#xff1a;display: inline-block, 则它的外在的盒子就是inline也就是不占据一行&#xff0c;而block则表示内部的元素具有块状…

go语言基础之格式化输出

1、fmt包的格式化输出输入 格式说明 格式 含义 %% 一个%字面量 %b 一个二进制整数值(基数为2)&#xff0c;或者是一个(高级的)用科学计数法表示的指数为2的浮点数 %c 字符型。可以把输入的数字按照ASCII码相应转换为对应的字符 %d 一个十进制数值(基数为10) %e 以科…

2021 年终总结

2021 年终总结 大家好&#xff0c;我是烤鸭&#xff0c;这是一篇无关技术的记录&#xff0c;总结一下这一年干了什么。 年初的目标 减肥 炒股回本 PMP拿证 多赚钱 多学习新技术 坚持写博客(一周一篇) 多看书 没达成的目标 减肥这个事&#xff0c;拖了好久&#xff0c…

[css] ::before和:after中单冒号和双冒号的区别是什么,这两个伪元素有什么作用?

[css] ::before和:after中单冒号和双冒号的区别是什么&#xff0c;这两个伪元素有什么作用&#xff1f; 区别&#xff1a;伪元素在css1中已经存在当时用单冒号&#xff0c;css3时做了修订用双冒号 ::before ::after表示伪元素用来区别伪类。作用&#xff1a;在元素前面&#x…

[css] css常用的布局方式有哪些?

[css] css常用的布局方式有哪些&#xff1f; 1&#xff1a;圣杯布局 2&#xff1a;双飞翼 3&#xff1a;flex个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢迎大家一起讨论 主目录 与歌谣一起通关前端面试题

记一次线上cpu飙升100%的排查过程

大家好&#xff0c;我是烤鸭&#xff1a; 最近没怎么写技术文章&#xff0c;还是得回归下初心&#xff0c;正好前几天出现个线上问题&#xff0c;记录下排查过程。 问题描述 某个时间点&#xff0c;接收到接口响应慢报警。 过一会收到服务器cpu可用率低(<10%)报警。 去c…

[css] 对比下px、em、rem有什么不同?

[css] 对比下px、em、rem有什么不同&#xff1f; px是css中的逻辑像素&#xff0c;和移动端的物理像素之间会有一个比值dpr em是指相对于父元素的大小 rem中的r就是root&#xff0c;也就是相对于root元素的大小&#xff08;html标签&#xff09;个人简介 我是歌谣&#xff0c…

Node.js(爱前端) 一

一 Node.js 简介 1.1 官网 https://nodejs.org/en/ 官网介绍&#xff1a; Node.js是一个构建在 Chrome 浏览器V8引擎上的 JavaScript 运行环境。 Node.js 使用了事件驱动、非阻塞I/O模型&#xff0c;这些都使它轻量、好用。 Node.js 的包生态&#xff08;npm&#xff09;&#…

记一次线上服务假死排查过程

大家好&#xff0c;我是烤鸭&#xff1a; 最近线上问题有点多啊&#xff0c;分享一个服务假死的排查过程。 问题描述 9点10分&#xff0c;收到进程无响应报警(一共6台机器&#xff0c;有1台出现)&#xff0c;后来又有1台出现。 排查思路 首先确认是否误报或者网络抖动&…

[css] 简述下你理解的优雅降级和渐进增强

[css] 简述下你理解的优雅降级和渐进增强 优雅降级&#xff0c;先做好一个完善的具备完整体验的版本&#xff0c;再向下做兼容。 渐进增强&#xff0c;先做好一个可以基本正常使用的版本&#xff0c;再慢慢丰富体验和内容。个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前…

vue小记

1.vue绑定属性&#xff0c;点击事件 1.<!-- 完整语法 --> <a v-bind:href"url">...</a><!-- 缩写 --> <a :href"url">...</a>2.<!-- 完整语法 --> <a v-on:click"doSomething">...</a>&l…

nacos注册中心自动上下负载

大家好&#xff0c;我是烤鸭&#xff1a; 还有2天就过年了&#xff0c;祝大家新年快乐。最近好久没写技术文章了&#xff0c;还是得回归下主业&#xff0c;今天分享下nacos注册中心自动上下负载的方式和组件。 组件版本 <properties><java.version>1.8</java.v…