本文始发于我的公众号:TechFlow,原创不易,求个关注程序员
今天是Python专题的第13篇文章,上一篇文章当中咱们介绍了Python装饰器的定义和基本的用法,这篇文章咱们一块儿来学习一下Python装饰器的一些进阶使用方法。对装饰器不太熟悉,或者错过了上篇内容的小伙伴能够点击下方传送门。web
以前的文章当中咱们从前到后仔细推到了一下装饰器的本质和用途,也学会了它的基本用法,已经足够应付80%的场景了。可是总有20%的场景使用基本的方法解决不了,这个时候就须要咱们学习更多、更全的其余用法。编程
好比我想要经过一个参数控制装饰器的功能,这个问题其实很常见。就拿记录时间来讲,咱们都知道时间能够记录成不少种格式,好比能够记成2020-05-04也能够记录成20200504,还能够记录成04/05/2020,若是是后端还会记录时间的时间戳。好比说咱们如今实现了一个记录日志的装饰器,用来给咱们的方法打上日志,如今咱们想要控制记录日志的时候打印出来的时间格式,这个需求使用最简单的装饰器就没有办法解决了。后端
这个时候,若是想要解决问题,就必须引入参数,也就是说咱们必需要在装饰器当中加入参数才行。但问题来了,这个参数怎么加,加在哪里呢?闭包
在咱们介绍具体的用法以前,咱们先来回顾一下装饰器的代码:编辑器
函数式编程
def mydec(func):
@wraps(func)
def mywrap(*args, **kw):
print('hello this is decorator1')
func(*args, **kw)
return mywrap
复制代码@mydec def helloWorld(): print('hello, world') 复制代码
这个就是咱们上次讲的最简单的那种装饰器,假如说咱们这个时候但愿传入一个参数type,能够控制装饰器的输出结果。就像这样:函数
@mydec(type_='test')
def helloWorld():
print('hello, world')
复制代码
咱们可能会想是否是应该在mydec这个方法的参数里面加上一个type_,可是若是你试一下就好发现这样是不行的,会获得一个error:工具
Error错误的字面意思很好理解,可是缘由却使人费解。这个Error是说函数mydec少了一个必选参数func,这个func就是咱们要包装的函数,可是这个不是自动传入的吗,怎么会提示咱们少了这个参数呢?
若是这个问题的本质不能理解的话,那么装饰器就很难大成了,由于只有理解清楚了这一点,才能理解后面装饰器各类稀奇古怪的进阶用法。可是很坑爹的是,不少资料当中都只是简单地介绍了怎么用,不多会探究其中背后的缘由,这会让初学者在学习的时候陷入费解。我在学习的时候也花了不少心思,才终于搞明白,说穿了很简单,可是想通不容易。
其实这样会报错的主要缘由是注解当中有参数和没有参数的装饰器是彻底不一样的。
咱们来回顾一下不加参数的装饰器的用法,好比:
@mydec
def hello_world():
pass
复制代码
咱们执行hello_world()的时候,等价于执行mydec(hello_world)()。看明白了吗,咱们把这行代码展开,它实际上是下面这两行代码共同执行的结果:
cur = mydec(hello_world)
cur()
复制代码
若是hello_world这个函数带上参数呢?
@mydec
def hello_world(*args, **kw):
pass
复制代码
那么执行的时候它实际上是这样的:
cur = mydec(hello_world)
cur(*args, **kw)
复制代码
这个理解了以后,咱们继续往下,如今咱们想要将一个参数传给装饰器,按照咱们的想法下面这两段代码应该是同样的。
@mydec(type_='test')
def helloWorld():
print('hello, world')
cur = mydec(hello_world, type_) cur() 复制代码
可是很遗憾的是,Python解释器当中并非这么设计的。它对加上了参数的装饰器多作了一层封装,也就是说上面传入参数的hello_world函数执行的时候等价于下面这段代码:
cur1 = mydec(type_)
cur2 = cur1(hello_world)
cur2()
复制代码
正是由于额外多封装了一层,因此函数和装饰器的参数传入装饰器的顺序是不一样的,顺序也是不同的。明白了这点以后就简单不少了,既然Python解释器在解释装饰器参数的时候多增长了一层,那么若是咱们想要实现带参数的装饰器,只须要也在装饰器当中多封装一层就能够了。好比能够写成这样:
def mydec(type_=None):
def decorate(func):
@wraps(func)
def mywrap():
if type_ is not None:
print(type_)
func()
return mywrap
return decorate
复制代码
这样咱们再执行就能够了:
到这里看似一切都很完美,但其实有一个很大的问题被咱们忽略了。
这个问题就是默认参数问题,在前面咱们定义装饰器的时候,将type_这个参数设置成了可选的。这也很符合咱们实际状况,如非必要,参数能省略就省略。可是这就致使了一个问题,对于不用加上参数的装饰器,有些人习惯写成mydec(),有些人习惯写成mydec。若是咱们试一下mydec,就会发现这样写会报错:
这个报错和上面的报错如出一辙,出现的缘由也是同样的,都是少了func参数。可是很奇怪啊,为何会少了func呢?
缘由很简单,由于咱们把括号去掉,装饰器又回到了以前的两层结构!
cur = mydec(hello_world)
cur(*args, **kw)
复制代码
这就很坑爹了,咱们装饰器的结构确定是不能改变的,若是使用两层结构就没办法传入参数了,可是若是不传参的时候怎么办,难道就只能强制程序员统一风格所有加上括号吗?这固然也是一个办法,那还有没有更好的办法呢?有没有办法统一这两种逻辑呢?
固然是有的,为了解决这个问题,咱们须要用到一个新的工具,叫作偏函数。
偏函数很好理解,它本意也是一个高阶函数,其实就是闭包。偏函数的使用场景针对多参数的函数,经过使用偏函数,能够固定若干个参数的传值,从而起到简化函数传参的做用。咱们来看一个例子,咱们建立一个pow函数,用来计算x的n次方:
import math
def pow(x, n):
return math.pow(x, n)
复制代码
这个函数须要传入x和n两个参数,若是咱们当前只须要计算平方,咱们可使用闭包,固定其中的参数n,生成一个新的函数来作到这点。好比:
def mypow(n):
def func(x):
return pow(x, n)
return func
复制代码
偏函数的本质就是这样一个闭包,只不过它简化了咱们的代码而已:
from functools import partial
pow2 = partial(pow, n=2) pow2(6) 复制代码
使用偏函数咱们只须要传入待加工的原函数,以及固定的参数值便可。咱们把偏函数用在装饰器当中,就能够解决刚才的问题。回忆一下,不带参数的装饰器是两层函数嵌套,而带上参数的是三层嵌套。那么咱们使用partial,专门为带上参数的状况额外增长一层嵌套便可:
def mydec(func=None, type_=None):
# 不带参数的话,func会是None,这时候咱们固定参数便可
if func is None:
return partial(mydec, type_=type_)
复制代码 @wraps(func) def mywrap(): if type_ is not None: print(type_) func() return mywrap 复制代码
咱们来看下这其中的细节,当咱们不传入参数的时候,咱们其实执行的是cur = mydec(func),这个时候func不为空,那么不会触发if中的语句,因此会直接返回mywrap。若是传入参数,这时候func是None,会触发if中的partial。注意这里咱们在partial当中传入的函数依然是mydec,也就是说咱们固定了type_这个参数,调用的话依然返回的是mywrap,至关于咱们经过partial额外在两层结构当中专门为带参数的状况增长了一层,统一了逻辑。
今天的概念比以前的装饰器要复杂不少,一时可能并很差理解,其实这是很是正常的。这不只仅是装饰器的问题,也不只是Python的问题,归根结底这是函数式编程的特性致使的。函数式编程的优势就是高度灵活,使用很是方便,但缺点也很明显,代码难以维护,阅读难度高,理解起来也不简单。典型的初学简单,精深很是难的典型。因此若是你们以为一时理解不了,这并非大家的问题,一方面咱们须要培养本身函数传编程的思惟,另外一方面咱们也须要熟悉Python中装饰器的使用方法。
最后说点题外话,因为只狼和仁王,最近有点迷上了硬核游戏。刚开始玩的时候,以为很是困难,常常卡关,一个boss死个几十次是屡见不鲜。等到了后来,慢慢找到了诀窍,瞬间发现这类游戏甚至全部游戏都变得简单了。
这不只仅是我熟悉了,更多的是由于玩游戏的时候也开始思考了,开始思考这些boss设计了哪些招数?设计者给咱们留下了哪些操做的空间对付它?有哪些规律可循?思考的多了,诀窍也就有了。打多了以后,不少boss就只剩下了初见难,只要打个两三次熟悉了套路,就能够过关了。慢慢地我发现生活当中的不少事情其实和游戏中的boss同样,只是初见难,第一次见到的时候以为无从下手,以为难以理解,以为庞然大物,因此很难。但只要有一颗坚毅、勇敢的心,学会冷静理智去分析,其实不过只是纸老虎而已。
但愿能给你们一点小小的启发,但愿你们面前的困难都只是纸老虎,但愿你们都能找到本身的勇气。
今天的文章就到这里,原创不易,须要你的一个关注,你的举手之劳对我来讲很重要。