五分钟学会Python装饰器,看完面试不再慌
本文始发于个人公众号:TechFlow,原创不易,求个关注
今天是Python专题的第12篇文章,我们来看看Python装饰器。
一段囧事
差不多五年前面试的时候,我就领教过它的重要性。那时候我Python刚刚初学乍练,看完了廖雪峰大神的博客,就去面试了。我应聘的并不是一个Python的开发岗位,但是JD当中写到了需要熟悉Python。我看网上的面经说到Python经常会问装饰器,我当时想的是装饰器我已经看过了,应该问题不大……
没想到面试的时候还真的问到了,面试官问我Python当中的装饰器是什么。由于紧张和遗忘,我支支吾吾了半天也没答上来。我隐约听到了电话那头的一声叹息……
时隔多年,我已经不记得那是一家什么公司了(估计规模也不大),但装饰器很重要这个事情给我深深打下了烙印。
装饰器本质
如今如果再有面试官问我Python中的装饰器是什么,我一句话就能给回答了,倒不是我装逼,实际上也的确只需要一句话。Python中的装饰器,本质上就是一个高阶函数。
你可能不太清楚高阶函数的定义,没关系,我们可以类比一下。在数学当中高阶导数,比如二次导数,表示导数的导数。那么这里高阶函数自然就是函数的函数,结合我们之前介绍过的函数式编程,也就是说是一个返回值是函数的函数。但是这个定义是充分不必要的,也就是说装饰器是高阶函数,但是高阶函数并不都是装饰器。装饰器是高阶函数一种特殊的用法。
任意参数
在介绍装饰器的具体使用之前,我们先来了解和熟悉一下Python当中的任意参数。
Python当中支持任意参数,它写成*args, **kw。表示的含义是接受任何形式的参数。
举个例子,比如我们定义一个函数:
def exp(a, b, c='3', d='f'):
print(a, b, c, d)
我们可以这样调用:
args = [1, 3]
dt = {'c': 4, 'd': 5}
exp(*args, **dt)
最后输出的结果是1, 3, 4, 5。也就是说我们用一个list和dict可以表示任何参数。因为Python当中规定必选参数一定写在可选参数的前面,而必选参数是可以不用加上名称标识的,也就是可以不用写a=1,直接传入1即可。那么这些没有名称标识的必选参数就可以用一个list来表示,而可选参数是必须要加上名称标识的,这些参数可以用dict来表示,这两者相加可以表示任何形式的参数。
注意我们传入list和dict的时候前面加上了*和**,它表示将list和dict当中的所有值展开。如果不加的话,list和dict会被当成是整体传入。
所以如果一个函数写成这样,它表示可以接受任何形式的参数。
def exp(*args, **kw):
pass
定义装饰器
明白了任意参数的写法之后,装饰器就不难了。
既然我们可以用*args, **kw接受任何参数。并且Python当中支持一个函数作为参数传入另外一个函数,如果我们把函数和这个函数的所有参数全部传入另外一个函数,那么不就可以实现代理了吗?
还是刚才的例子,我们额外增加一个函数:
def exp(a, b, c='3', d='f'):
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 wraps
def wrapexp(func):
def wrapper(*args, **kwargs):
print('this is a wrapper')
func(*args, **kwargs)
return wrapper
@wrapexp
def exp(a, b, c='3', d='f'):
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 time
from functools import wraps
def timethis(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(func.__name__, end-start)
return result
return wrapper
这也是装饰器最大的用途,可以在不修改函数内部代码的前提下,为它包装一些额外的功能。
元信息
我们之前说过装饰器的本质是高阶函数,所以我们也可以和高阶函数一样来调用装饰器,比如下面这样:
def exp(a, b, c='3', d='f'):
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进阶必学的技能之一。想要熟练掌握这门语言,灵活运用,看懂大佬的源码,装饰器是必须会的东西。
希望大家都能有所收获,原创不易,厚颜求个赞和关注~
五分钟学会Python装饰器,看完面试不再慌的更多相关文章
- 理解 Python 装饰器看这一篇就够了
讲 Python 装饰器前,我想先举个例子,虽有点污,但跟装饰器这个话题很贴切. 每个人都有的内裤主要功能是用来遮羞,但是到了冬天它没法为我们防风御寒,咋办?我们想到的一个办法就是把内裤改造一下,让它 ...
- 如何理解Python装饰器
如何理解Python装饰器?很多学员对此都有疑问,那么上海尚学堂python培训这篇文章就给予答复. 一.预备知识 首先要理解装饰器,首先要先理解在 Python 中很重要的一个概念就是:“函数是 F ...
- 利用世界杯,读懂 Python 装饰器
Python 装饰器是在面试过程高频被问到的问题,装饰器也是一个非常好用的特性, 熟练掌握装饰器会让你的编程思路更加宽广,程序也更加 pythonic. 今天就结合最近的世界杯带大家理解下装饰器. 德 ...
- 你必须学写 Python 装饰器的五个理由
你必须学写Python装饰器的五个理由 ----装饰器能对你所写的代码产生极大的正面作用 作者:Aaron Maxwell,2016年5月5日 Python装饰器是很容易使用的.任何一个会写Pytho ...
- Python基础(五) python装饰器使用
这是在Python学习小组上介绍的内容,现学现卖.多练习是好的学习方式. 第一步:最简单的函数,准备附加额外功能 # -*- coding:gbk -*- '''示例1: 最简单的函数,表示调用了两次 ...
- (转)python装饰器进阶一
Python装饰器进阶之一 先看例子 网上有很多装饰器的文章,上来说半天也没让人看明白装饰器到底是个什么,究竟有什么用,我们直接来看几个例子. Python递归求斐波那契数列 def fibonacc ...
- [分享] 史上最简单的封装教程,五分钟学会封装系统(以封装Windows 7为例)
[分享] 史上最简单的封装教程,五分钟学会封装系统(以封装Windows 7为例) 踏雁寻花 发表于 2015-8-23 23:31:28 https://www.itsk.com/thread-35 ...
- Python装饰器详解
python中的装饰器是一个用得非常多的东西,我们可以把一些特定的方法.通用的方法写成一个个装饰器,这就为调用这些方法提供一个非常大的便利,如此提高我们代码的可读性以及简洁性,以及可扩展性. 在学习p ...
- Python装饰器由浅入深
装饰器的功能在很多语言中都有,名字也不尽相同,其实它体现的是一种设计模式,强调的是开放封闭原则,更多的用于后期功能升级而不是编写新的代码.装饰器不光能装饰函数,也能装饰其他的对象,比如类,但通常,我们 ...
随机推荐
- 9.Maven的生命周期
Clean Lifecycle: 在进行真正的构建之前进行一些清理工作. Default Lifecycle :构建的核心部分,编译,测试,打包,部署等等. Site Lifecycle : 生成项目 ...
- ASP.NET Core MVC通过IViewLocationExpander扩展视图搜索路径
IViewLocationExpander API ExpandViewLocations Razor视图路径,视图引擎会搜索该路径. PopulateValues 每次调用都会填充路由 项目目录如下 ...
- Ubuntu下已安装Anaconda但出现conda: command not found错误解决办法
原因:环境未配置 执行[vim ~/.bashrc]命令,进入配置文件,在最后一行按'o'插入一行,并添加语句: export PATH=/home/duanyongchun/anaconda3/bi ...
- [HDU1029]Ignatius and the Princess IV<桶 水题>
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1029 题目大意: 多组数据,每组数据先给一个n,然后给n各数字,找出n各数字中出现了至少(n+1)/2 ...
- [vijos1144]小胖守皇宫<树形dp>
题目链接:https://vijos.org/p/1144 woc我竟然A了,这道经典的树形dp或者说是树形dp的入门题我终于过了,虽然之前做过一些树形dp的题,但是这题开始还是一脸懵逼,dp方程如何 ...
- 《 OO第一作业周期(前四周)总结 》
作为一名软件工程的大学生,很高兴能够以这样一种方式,实现对博客编写零的突破.专业课老师也介绍了编写博客给我们带来的帮助,听了以后,我感觉到了培养出写博客的习惯,是一件多么有意义的事! 话不多说,让我们 ...
- 在非主线程里面使用NSTimer创建和取消定时任务
为什么要在非主线程创建NSTimer 将 timer 添加到主线程的Runloop里面本身会增加线程负荷 如果主线程因为某些原因阻塞卡顿了,timer 定时任务触发的时间精度肯定也会受到影响 有些定时 ...
- Redis 哨兵模式(Sentinel)
上一篇我们介绍了 redis 主从节点之间的数据同步复制技术,通过一次全量复制和不间断的命令传播,可以达到主从节点数据同步备份的效果,一旦主节点宕机,我们可以选择一个工作正常的 slave 成为新的主 ...
- go 基础安装
一.安装: 1.下载GO的地址:https://golang.org/dl/ 点击安装包进行安装(linux直接解压) 设置环境变量(linux) 1. export GOROOT=$PATH:/pa ...
- Java第十七天,Set接口
Set接口 1.特点 (1)不包含重复元素. (2)没有索引. (3)继承自Collection接口,所以Collection接口中的所有方法都适用于Set接口. 2.解析 (1)为什么不能包含重复元 ...