5 分钟全面掌握 Python 装饰器
♚
作者:吉星高照, 网易游戏资深开发工程师,主要工作方向为网易游戏 CDN 自动化平台的设计和开发,脑洞比较奇特,喜欢在各种非主流的领域研究制作各种不走寻常路的东西。
!
Python的装饰器是面试的常客,因为其写法复杂多变,经常忘记什么地方应该写哪种参数,新手学习起来也经常一头雾水,不怕不怕,看了这一篇你对装饰器的各种用法就全明白了。废话不多说,直接进入主题!
不带参数的函数,不带参数的装饰器
我们先来写一个简单的装饰器,实现将函数运行前后的情况记录下来。
def dec1(func): print(func) def _wrap(): print('before run') r = func() print('after run') return r return _wrap @dec1 def f1(): print('call f1')
上面只是定义了两个函数,运行后发现竟然有输出:
<function f1 at 0x7fa1585f8488>
仔细看看,原来是第一个 print 语句的输出。这说明装饰的函数还没有实际运行的时候,装饰器就运行过了,因为 @dec1 相当于单独的一个语句:
dec1(f1)
那我们来正式运行一下 f1 吧:
f1(1)
输出如下确实达到了预期的效果:
before run call f1 after run
不带参数的函数,带空参数的装饰器
后面我们还想要给装饰器加上参数呢,先试试用这个方式调用会发生什么情况:
@dec1()
输出了错误:
Traceback (most recent call last) <ipython-input-268-01cf93cf6907> in <module> 8 return _wrap 9 ---> 10 @dec1() 11 def f1(): 12 print('call f1') TypeError: dec1() missing 1 required positional argument: 'func'
它说 dec1 需要接受 func 这个参数才行,那我们改改,作为 f2 函数吧:
def dec2(): def _wrap(func): print(func) print('before run') return func return _wrap @dec2() def f2(): print('call f2') f2()
这下可以了:
<function f2 at 0x7fa1585af2f0> before run call f2
可是这个结构和原来有点不同了,而且, after run 要写在哪里呢?很愁人地又改了一版,把它叫做 f2x 吧,对比 dec1 ,又多了一层函数 dec2x_w ,开始有点晕:
def dec2x_w(): def dec2x(func): print(func) def _wrap(): print('before run') r = func() print('after run') return r return _wrap return dec2x @dec2x_w() def f2x(): print('call f2x') f2x()
运行一下看看,确实是想要的:
<function f2x at 0x7fa1585af950> before run call f2x after run
后面我们就不加 before/after run 了。
带参数的函数,不带参数的装饰器
函数 f2x 想要接受参数呢?我们把它叫做 a 吧,比较简单,不就是 _wrap 的参数吗,加上就是了,而且又回到了只有两层函数就可以实现了:
def dec3(func): print(func) def _wrap(param): print(param) r = func(param) return r return _wrap @dec3 def f3(a): print('call f3', a) f3(1)
很争气地输出了结果:
<function f3 at 0x7fa158719620> 1 call f3 1
带参数的函数,带参数的装饰器
下面我们实现一个装饰器,传入一个整数,和函数传入的参数做个加法:
def dec4_w(d_param): print(d_param) def dec4(func): print(func) def _wrap(param): print(param) r = func(param + d_param) return r return _wrap return dec4 @dec4_w(2) def f4(a): print('call f4', a) f4(1)
输出 1+2=3 :
2 <function f4 at 0x7fa1585af598> 1 call f4 3
从调用的装饰器往里看,注意这三层函数的形参,第一层是装饰器的参数,第二层是函数,第三层是函数的参数,很有规律的排列,先记一下这个规律(要考的)。带两个参数的也是一样的,接着写就可以了:
def dec5_w(d_param_1, d_param_2): print(d_param_1, d_param_2) def dec5(func): print(func) def _wrap(param): print(param) r = func(param + d_param_1 + d_param_2) return r return _wrap return dec5 @dec5_w(2, 3) def f5(a): print('call f5', a) f5(1)
输出 1+2+3=6 :
2 3 <function f5 at 0x7fa1586237b8> 1 call f5 6
如果用不定数量的位置参数,就用 *args 作为形参吧:
def dec6_w(*args): d_param_1, d_param_2, = args print(d_param_1, d_param_2) def dec6(func): print(func) def _wrap(*args): param = args[0] print(param) r = func(param + d_param_1 + d_param_2) return r return _wrap return dec6 @dec6_w(2, 3) def f6(a): print('call f6', a) f6(1) print(f6.__name__)
顺便输出了一下 f6 的函数名:
2 3 <function f6 at 0x7fa1586236a8> 1 call f6 6 _wrap
咦!怎么肥四!!! f6 怎么是里面那个 _wrap 的名字呢?不怕不怕, functools 提供了一个 wraps 装饰器专治各种不服(在装饰器里面放上另一个装饰器):
from functools import wraps def dec7_w(*args): d_param_1, d_param_2, = args print(d_param_1, d_param_2) def dec7(func): print(func) @wraps(func) def _wrap(*args): param = args[0] print(param) r = func(param + d_param_1 + d_param_2) return r return _wrap return dec7 @dec7_w(2, 3) def f7(a): print('call f7', a) f7(1) print(f7.__name__)
这下正常输出 f7 了:
2 3 <function f7 at 0x7fa1585f8510> 1 call f7 6 f7
装饰器类(带参数的函数,带参数的装饰器)
用函数做装饰器局限性太多了,用相同的调用方法,把函数 f7 改成类怎么样?emmm…改造工程有点大,直接看看成品吧:
from functools import wraps class dec8_c: def __init__(self, *args): self.d_param_1, self.d_param_2, = args print(self.d_param_1, self.d_param_2) def __call__(self, func): print(func) @wraps(func) def _wrap(param): print(param) r = func(param + self.d_param_1 + self.d_param_2) return r return _wrap @dec8_c(2, 3) def f8(a): print('call f8', a) f8(1) print(f8.__name__)
看看是不是实现了一样的效果:
2 3 <function f8 at 0x7fa1585f8048> 1 call f8 6 f8
虽然使用了 __call__ ,但这里的 __init__ 不能省略(因为它需要知道参数个数),否则会出现这个错误:
Traceback (most recent call last) <ipython-input-276-1634a47057a2> in <module> 14 return dec8 15 ---> 16 @dec8_c(2, 3) 17 def f8(a): 18 print('call f8', a) TypeError: dec8_c() takes no arguments
同时还可以发现, __call__ 只需要两层函数了,去掉了第二层,直接把 _wrap 的函数体往上提了一层!
装饰器类(带参数的函数,不带参数的装饰器)
大概是吃饱了撑着,又想要实现一开始那个不带参数的装饰器了,那就继续敲敲打打一番看看:
class dec9_c: def __init__(self, func): print(func) self.func = func self.__name__ = func.__name__ def __call__(self, param): print(param) func = self.func r = func(param) return r @dec9_c def f9(a): print('call f9', a) f9(1) print(f9.__name__)
赶快运行看看:
<function f9 at 0x7fa1585f8730> 1 call f9 1 f9
咦, f9 的函数名可以直接打印,这下都不用 @wraps 了呢!呃,再仔细看看,这写法好像有些不一样啊:
- dec8_c 的 init 带的是装饰器的参数,可是 dec9_c 带的是装饰器函数自己!
- 所以实际调用的函数名也可以在 init 中传给它了哦!
- 而且 call 函数也简洁了很多,看来有没有参数真的有很大区别呢!
这里先做个总结,装饰器使用函数名形式(不带括号)和使用函数调用形式(带括号和参数)在实现上是不同的,因为前者是函数本身,而后者是从装饰器函数中返回的函数。这也是 f2 相比 f1 缺少了记录 after run 的原因,因为 dec1 直接调用了 f2 ,而 dec2 先运行得到函数,再把函数返回去调用 f2 。用装饰器类就可以解决这个问题,因为它是对 __call__ 的调用,只需要自己定义一下就可以了。
上面的 f9 要写两个函数,能不能写得和 f1 一样简洁?当然是可以的,使用 __new__ 大法:
from functools import wraps class dec9x_c: def __new__(self, func): print(func) @wraps(func) def dec9x(param): print(param) r = func(param) return r return dec9x @dec9x_c def f9x(a): print('call f9x', a) f9x(1) print(f9x.__name__)
这样就避开了函数调用,不用打call了(定义 __call__ 函数),快看它来了:
<function f9x at 0x7fa158623bf8> 1 call f9x 1 f9x
5 分钟全面掌握 Python 装饰器的更多相关文章
- 五分钟学会Python装饰器,看完面试不再慌
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是Python专题的第12篇文章,我们来看看Python装饰器. 一段囧事 差不多五年前面试的时候,我就领教过它的重要性.那时候我Pyt ...
- Python 装饰器入门(上)
翻译前想说的话: 这是一篇介绍python装饰器的文章,对比之前看到的类似介绍装饰器的文章,个人认为无人可出其右,文章由浅到深,由函数介绍到装饰器的高级应用,每个介绍必有例子说明.文章太长,看完原文后 ...
- (转)python装饰器进阶一
Python装饰器进阶之一 先看例子 网上有很多装饰器的文章,上来说半天也没让人看明白装饰器到底是个什么,究竟有什么用,我们直接来看几个例子. Python递归求斐波那契数列 def fibonacc ...
- 关于python装饰器
关于python装饰器,不是系统的介绍,只是说一下某些问题 1 首先了解变量作用于非常重要 2 其次要了解闭包 def logger(func): def inner(*args, **kwargs) ...
- python装饰器通俗易懂的解释!
1.python装饰器 刚刚接触python的装饰器,简直懵逼了,直接不懂什么意思啊有木有,自己都忘了走了多少遍Debug,查了多少遍资料,猜有点点开始明白了.总结了一下解释得比较好的,通俗易懂的来说 ...
- Python 装饰器学习
Python装饰器学习(九步入门) 这是在Python学习小组上介绍的内容,现学现卖.多练习是好的学习方式. 第一步:最简单的函数,准备附加额外功能 1 2 3 4 5 6 7 8 # -*- c ...
- python 装饰器修改调整函数参数
简单记录一下利用python装饰器来调整函数的方法.现在有个需求:参数line范围为1-16,要求把9-16的范围转化为1-8,即9对应1,10对应2,...,16对应8. 下面是例子: def fo ...
- python 装饰器学习(decorator)
最近看到有个装饰器的例子,没看懂, #!/usr/bin/python class decorator(object): def __init__(self,f): print "initi ...
- Python装饰器详解
python中的装饰器是一个用得非常多的东西,我们可以把一些特定的方法.通用的方法写成一个个装饰器,这就为调用这些方法提供一个非常大的便利,如此提高我们代码的可读性以及简洁性,以及可扩展性. 在学习p ...
随机推荐
- (转)null和NULL和nullptr和””区别
突然想到这个有趣的问题:C语言和C++对大小写是敏感的,也就是说null和NULL是区别对待的.NULL代表空地址,null只是一个符号.便来深究,看了很多资料,总结如下: 其实null和NULL都是 ...
- Intent 显示意图 隐式意图
//显式意图 :必须指定要激活的组件的完整包名和类名 (应用程序之间耦合在一起) // 一般激活自己应用的组件的时候 采用显示意图 //隐式意图: 只需要指定要动作和数据就可以 ( 好处应用程序之 ...
- java切换jdk版本
目的:将jdk1.7切换为jdk1.6 1.原本安装了jdk1.7,环境变量也是配置的1.7相关路径,在cmd下输入[java -version]后,显示 [ C:\Users\Administrat ...
- java课程课后作业190502之单词统计
自己想的方法一直都不是很好,但是又一直忘了改自己的算法,只能硬着头皮接着用自己以前的老方法了 第0步:输出某个英文文本文件中 26 字母出现的频率,由高到低排列,并显示字母出现的百分比,精确到小数点后 ...
- JAVAEE 和项目开发(第三课:HTTP的请求头和请求方式)
HTTP 协议之请求格式 请求格式的结构:请求行:请求方式.请求的地址和 HTTP 协议版本 请求头:消息报头,一般用来说明客户端要使用的一些附加信息 空行: 位于请求行和请求数据之间,空行是必须 ...
- 归并排序(包含逆序数对的个数51Nod1019)
归并排序是效率很好的排序方式,和快排效率一样高,但在稳定性上优于快排,下面我们来介绍归并排序. 归并排序运用递归将序列不断二分(其原理就是分治),就像一棵树不断向下分支,最后分到只剩一个元素,这样这个 ...
- flink和spark Streaming中的Back Pressure
Spark Streaming的back pressure 在讲flink的back pressure之前,我们先讲讲Spark Streaming的back pressure.Spark Strea ...
- Java算法练习——两数之和
题目链接 题目描述 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是,你不能重复利 ...
- 【每日Scrum】第七天冲刺
一.计划会议内容 界面ui制作,主界面进度 二.任务看板 三.scrum讨论照片 四.产品的状态 无 五.任务燃尽图
- Codeforces 405D 数学问题
真是脑残...擦 具体题解在这里 http://www.cnblogs.com/windysai/p/3619222.html 原本我为了防止两个数冲突,设置了好多判断,结果发现,如果两个数冲突,另外 ...