五分钟学会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装饰器由浅入深
装饰器的功能在很多语言中都有,名字也不尽相同,其实它体现的是一种设计模式,强调的是开放封闭原则,更多的用于后期功能升级而不是编写新的代码.装饰器不光能装饰函数,也能装饰其他的对象,比如类,但通常,我们 ...
随机推荐
- 【宇哥带你玩转MySQL】索引篇(一)索引揭秘,看他是如何让你的查询性能指数提升的
场景复现,一个索引提高600倍查询速度? 首先准备一张books表 create table books( id int not null primary key auto_increment, na ...
- 操作系统-IO与显示器
1. 让外设工作起来 只要给相应的控制器中的寄存器发一个指令 向设备控制器的寄存器写不就可以了吗? 需要查寄存器地址.内容的格式和语义.操作系统需要给用户提供一个简单视图---文件视图,这样方便 总的 ...
- 多线程学习笔记(四)---- Thread类的其他方法介绍
一.wait和 sleep的区别 wait可以指定时间也可以不指定时间,而sleep必须指定时间: 在同步中时,对cpu的执行权和锁的处理不同: wait:释放执行权,释放锁:释放锁是为了别人noti ...
- Ceph学习笔记(3)- Monitor
Ceph学习笔记(3)- Monitor 前言: Ceph将cluster map与placement rule合并为一张表称为crush map,作为集群表的一部分.由Monitor对集群表的副 ...
- MTK Android 平台语言支持状态
Language English Name Chinese Name Code GB ICS JB KK L العربية Arabic(Israel) 阿拉伯语(以色列) ar_IL Y Y ...
- Linux bash篇(三 数据流重定向)
1> 以覆盖的方式将正确的数据输出到文件或设备上 1>> 以追加的方式将正确的数据输出到文件或设备上 2> 以覆盖的方式将错误的数据输 ...
- 21.1 Math(数学运算)方法使用 、工具类
package day21_static.meathDemo; //Math: 包含一些基本的数学运算方法 //从api中搜Math,它都用的static修饰. public class MethDe ...
- 关于SQLAlchemy ORM框架
SQLAlchemy 1.介绍 SQLAlchemy是一个基于Python实现的ORM框架.该框架建立在 DB API之上,使用关系对象映射进行数据库操作,简言之便是:将类和对象转换成SQL,然后使用 ...
- SQL数据类型:nchar,char,varchar,nvarchar 的区别和应用场景
概括: char:固定长度,存储ANSI字符,不足的补英文半角空格.CHAR存储定长数据很方便,CHAR字段上的索引效率级高,比如定义CHAR(10),那么不论你存储的数据是否达到了10个字节,都要占 ...
- 数据结构和算法(Golang实现)(11)常见数据结构-前言
常见数据结构及算法 数据结构主要用来组织数据,也作为数据的容器,载体. 各种各样的算法,都需要使用一定的数据结构来组织数据. 常见的典型数据结构有: 链表 栈和队列 树 图 上述可以延伸出各种各样的术 ...