怎么做公司网站需要什么,网站建设需要哪种人才,网站 案例展示,asp网站怎么仿站本文始发于个人公众号#xff1a;TechFlow#xff0c;原创不易#xff0c;求个关注今天是Python专题的第12篇文章#xff0c;我们来看看Python装饰器。一段囧事差不多五年前面试的时候#xff0c;我就领教过它的重要性。那时候我Python刚刚初学乍练#xff0c;看完了廖雪…本文始发于个人公众号TechFlow原创不易求个关注今天是Python专题的第12篇文章我们来看看Python装饰器。一段囧事差不多五年前面试的时候我就领教过它的重要性。那时候我Python刚刚初学乍练看完了廖雪峰大神的博客就去面试了。我应聘的并不是一个Python的开发岗位但是JD当中写到了需要熟悉Python。我看网上的面经说到Python经常会问装饰器我当时想的是装饰器我已经看过了应该问题不大……没想到面试的时候还真的问到了面试官问我Python当中的装饰器是什么。由于紧张和遗忘我支支吾吾了半天也没答上来。我隐约听到了电话那头的一声叹息……时隔多年我已经不记得那是一家什么公司了(估计规模也不大)但装饰器很重要这个事情给我深深打下了烙印。装饰器本质如今如果再有面试官问我Python中的装饰器是什么我一句话就能给回答了倒不是我装逼实际上也的确只需要一句话。Python中的装饰器本质上就是一个高阶函数。你可能不太清楚高阶函数的定义没关系我们可以类比一下。在数学当中高阶导数比如二次导数表示导数的导数。那么这里高阶函数自然就是函数的函数结合我们之前介绍过的函数式编程也就是说是一个返回值是函数的函数。但是这个定义是充分不必要的也就是说装饰器是高阶函数但是高阶函数并不都是装饰器。装饰器是高阶函数一种特殊的用法。任意参数在介绍装饰器的具体使用之前我们先来了解和熟悉一下Python当中的任意参数。Python当中支持任意参数它写成*args, **kw。表示的含义是接受任何形式的参数。举个例子比如我们定义一个函数def exp(a, b, c3, df):print(a, b, c, d)我们可以这样调用args [1, 3]dt {c: 4, d: 5}exp(*args, **dt)最后输出的结果是1 3 4 5。也就是说我们用一个list和dict可以表示任何参数。因为Python当中规定必选参数一定写在可选参数的前面而必选参数是可以不用加上名称标识的也就是可以不用写a1直接传入1即可。那么这些没有名称标识的必选参数就可以用一个list来表示而可选参数是必须要加上名称标识的这些参数可以用dict来表示这两者相加可以表示任何形式的参数。注意我们传入list和dict的时候前面加上了*和**它表示将list和dict当中的所有值展开。如果不加的话list和dict会被当成是整体传入。所以如果一个函数写成这样它表示可以接受任何形式的参数。def exp(*args, **kw):pass定义装饰器明白了任意参数的写法之后装饰器就不难了。既然我们可以用*args, **kw接受任何参数。并且Python当中支持一个函数作为参数传入另外一个函数如果我们把函数和这个函数的所有参数全部传入另外一个函数那么不就可以实现代理了吗还是刚才的例子我们额外增加一个函数def exp(a, b, c3, df):print(a, b, c, d)def agent(func, *args, **kwargs):func(*args, **kwargs)args [1]dt {b: 1, c: 4, d: 5}agent(exp, *args, **dt)装饰器的本质其实就是这样一个agent函数但是如果使用的时候需要手动传入会非常麻烦使用起来不太方便。所以Python当中提供了特定的库我们可以让装饰器以注解的方式使用大大简化操作from functools import wrapsdef wrapexp(func):def wrapper(*args, **kwargs):print(this is a wrapper)func(*args, **kwargs)return wrapperwrapexpdef exp(a, b, c3, df):print(a, b, c, d)args [1, 3]dt {c: 4, d: 5}exp(*args, **dt)在这个例子当中我们定义了一个wrapexp的装饰器。我们在其中的wrapper方法当中实现了装饰器的逻辑wrapexp当中传入的参数func是一个函数wrapper当中的参数则是func的参数。所以我们在wrapper当中调用func(*args, **kw)就是调用打上了这个注解的函数本身。比如在这个例子当中我们没有做任何事情只是在原样调用之前多输出了一行’this is a wrapper表示我们的装饰器调用成功了。装饰器用途我们理解了装饰器的基本使用方法之后自然而然地会问一个天然的问题学会了它究竟有什么用呢如果你从上面的例子当中没有领会到装饰器的强大不如让我用一个例子再来暗示一下。比如说你是一个程序员辛辛苦苦做出了一个功能写了好几千行代码上百个函数终于通过了审核上线了。这个时候你的产品经理找到了你说经过分析我们发现上线的功能运行速度不达标经常有请求超时你能不能计算一下每个函数运行的耗时方便我们找到需要优化的地方这是一个非常合理的请求但想想看你写了上百个函数如果每一个函数都要手动添加时间计算这要写多少代码万一哪个函数不小心改错了你又得一一检查并且如果要求严格的话你还得为每一个函数专门写一个单元测试……我想正常的程序员应该都会抗拒这个需求。但是有了装饰器就很简单了我们可以实现一个计算函数耗时的装饰器然后我们只需要给每一个函数加上注解就好了。import timefrom functools import wrapsdef timethis(func):def wrapper(*args, **kwargs):start time.time()result func(*args, **kwargs)end time.time()print(func.__name__, end-start)return resultreturn wrapper这也是装饰器最大的用途可以在不修改函数内部代码的前提下为它包装一些额外的功能。元信息我们之前说过装饰器的本质是高阶函数所以我们也可以和高阶函数一样来调用装饰器比如下面这样def exp(a, b, c3, df):print(a, b, c, d)args [1, 3]dt {c: 4, d: 5}f wrapexp(exp)f(*args, **dt)这样的方式得到的结果和使用注解是一样的也就是说我们加上注解的本质其实就是调用装饰器返回一个新的函数。既然和高阶函数是一样的那么就带来了一个问题我们使用的其实已经不再是原函数了而是一个由装饰器返回的新函数虽然这个函数的功能和原函数一样但是一些基础的信息其实已经丢失了。比如我们可以打印出函数的name来做个实验正常的函数调用__name__返回的都是函数的名称但是当我们加上了装饰器的注解之后就会发生变化同样我们输出加上了装饰器注解之后的结果我们会发现输出的结果变成了wrapper这是因为我们实现的装饰器内部的函数叫做wrapper。不仅仅是__name__函数内部还有很多其他的基本信息比如记录函数内描述的__doc____annotations__等等这些基本信息被称为是元信息这些元信息由于我们使用注解发生了丢失。有没有什么办法可以保留这些函数的元信息呢其实很简单Python当中为我们提供了一个专门的装饰器用来保留函数的元信息我们只需要在实现装饰器的wrapper函数当中加上一个注解wraps即可。def wrapexp(func):wraps(func)def wrapper(*args, **kwargs):print(this is a wrapper)func(*args, **kwargs)return wrapper加上了这个注解之后我们再来检查函数的元信息会发现它和我们预期一致了。总结了解了Python中的装饰器之后再来看之前我们用过的property, staticmethod等注解想必都能明白它们背后的实现其实也是装饰器。灵活使用装饰器可以大大简化我们的代码让我们的代码更加规范简洁还能灵活地实现一些特殊的功能。装饰器的用法很多今天介绍的只是其中最基本的在后续的文章当中还会继续和大家分享它更多其他的用法。在文章开始的时候我也说了装饰器是Python进阶必学的技能之一。想要熟练掌握这门语言灵活运用看懂大佬的源码装饰器是必须会的东西。希望大家都能有所收获原创不易厚颜求个赞和关注~