您的当前位置:首页正文

Python 入门 11 —— 闭包 、装饰器

2024-11-11 来源:个人技术集锦

一、专用函数

如果有10个函数都要用到同一种复杂计算,比如,计算变量的绝对值加变量的5倍(|x|+5x),那么我们首先想到应该是先定义一个专门进行这种复杂计算的函数,然后,让那10个函数来调用。例如:

def fff(x): return |x|+5x

def fun01(x): return ... + fff(x) - ...
def fun02(x): return ... * fff(x) / ...
......
def fun10(x): return ... + |fff(x)| * ...

当然,本例中 |x|+5x 不是很复杂,专门制作一个函数显示不是很必要,但如果复杂计算是这样的呢:

x^ 6 + 5 * x^ 5 + 6 * x^ 4 + 8 * x^ 3 + x! + |x|+5x + sinx + cosx

我们可以把像 fff(x) 这种专门进行某种特殊运算的函数称为专用函数。将共同的操作提取出来,制作成一个专用函数,然后让各种具体的函数来调用,应该是我们常用的方法,这种方法遵循的思路是由内而外的,或者说是由下而上的。

二、闭包

想像一个水果店,它卖5种水果,设计一个计算每天销售额的函数:

def sale(n1,n2,n3,n4,n5,p1,p2,p3,p4,p5): return n1*p1 + n2*p2 + n3*p3 + n4*p4 + n5*p5  # n————销量,p————价格)

这个函数显然有点啰嗦,但也没有办法:各咱水果的销售每天都会变,价格也不可能相同。

一段时间之后发现一个问题:各水果的价格不是天天变化,而是过几天变一次。也就是价格是相对固定的,针对这一情况有没有可能简化以上的函数呢?答案是:有。

def sale_fun(p1,p2,p3,p4,p5): 
    def bb_fun(n1,n2,n3,n4,n5):
        return n1*p1 + n2*p2 + n3*p3 + n4*p4 + n5*p5
    return bb_fun

以上根据函数嵌套的规则,在函数sale_fun()内又定义了一个函数bb_fun(),并且将函数bb_fun()返回。因为返回的是一个函数,所以调用 sale_fun() 实际得到的是一个函数。

sale_fun() 以水果价格为参数,得到的是当前价格下计算每天销售额的函数,例如:

f01 = sale_fun(1,2,3,4,5)   # f01 即价格为 (1,2,3,4,5) 时计算每天销售额函数

sale = f01(2,2,3,5,6); print(sale)   # 将每天的销量代入f01,就得到了当天的销售额:65

当价格不变化时,每天都可以用 f01 来计算销售额。

当价格变化时,可以根据新的价格,用sale_fun()再生成一个新的计算每天销售额函数。

f02 = sale_fun(2,4,6,8,10)   # f02 即为价格为 (2,4,6,8,10) 时计算每天销售额函数

sale = f02(2,2,3,5,6); print(sale)  # 将每天的销量代入f02,就得到了新价格下当天的销售额:130

像 bb_fun() 这样,在一个函数内部定义并被返回的函数,在 Python 中被称作:闭包。在一个闭包中,除了有功能,还携带有数据,例如,在bb_fun()中,除了有先乘后加这样计算销售额的功能,还有各种水果价格的数据,是一个半成品函数。通过闭包机制,可以随时“生产”出各种新条件下的函数,从而简化一些特定问题的解决方法。

在函数内部定义的函数,可以称之为“闭包函数”。

三、装饰器
在第一部分提到,制作成一个专用函数,然后让各种具体的函数来调用,遵循的是“由内而外”解决问题的思路,那如果遵循“由外而内”或“由上而下”解决问题的思路,结果会是怎样呢?

例如:有10个函数,在最后都要进行一步复杂计算,比如,把前面计算的结果加上5再扩大1000倍。例如:

def newfun01(x): return ((2*x) +5)*1000
def newfun02(x): return ((5+x) +5)*1000
......
def newfun10(x): return ((3*x-7) +5)*1000

每个函数到了最后都要进行同样的操作,那能不能遵循“由外而内”的思路,专门做了一个“加上5再扩大1000倍”的功能附加到各个函数上去呢?回答是:能。装饰器就是为此而生。

第一步:在一个函数内部定义(内嵌)一个具有“加上5再扩大1000倍”功能的函数,并返回。例如:

def zhshi(fff):
    def baibei(x):
        return (fff(x) +5)*1000
    return baibei

baibei 函数在 zhshi()中定义,并被返回,那这个函数不就是一个闭包吗?完全正确。baibei 函数就是一个闭包。但不同的是,外层函数在实际调用时,需要接收一个其它函数作为实参。也就是说,这个外层函数,接收的是函数,返回的也是函数。这样的外层函数就被称作“装饰函数”或“装饰器”,内部函数就是一个“闭包函数”。

第二步:在需要“加上5再扩大1000倍”这一功能的函数的定义前面加个“@ zhshi”这一句,这样新定义的函数就会被会自动添加上“加上5再扩大1000倍”的功能。例如:

@ zhshi
def fun01(x): return 2*x

@ zhshi
def fun02(x): return 5+x

@ zhshi
def fun10(x): return 3*x-7

print(fun01(3), fun02(3), fun10(3))   # 11000 13000 7000

由上可见,一旦定义了具有某种特定功能的装饰器,那么,在定义新函数时,只要在定义前加上“@ 特定装饰器”这一句,新定义函数就会被加上特定功能。

@ 装饰器
def 功能函数(参数):
    函数体

实际的效果等同于:

def 功能函数(参数):
    函数体
功能函数 = 装饰器(功能函数)

例如:

@ zhshi
def fun01(x): return 2*x

等同于:

def fun01(x): return 2*x
fun01 = zhshi(fun01)

在实际调用时,装饰器不会接收任何的实参,实参直接被赋给装饰器内部的闭包函数,功能函数形参将在闭包函数内部被赋值。

明白了以上等同的原理,如何依据特定的功能设计装饰器,就应该比较清楚了。

解决实际问题时,是遵循“由内而外”的思路,还是遵循“由外而内”的思路,是根据实际情况决定的,有时两种思路都行得通,且几乎一样地便捷,比如以上第一部分和第三部分的例子,用哪种方法都是可以的。但有时一种方法可能要比另外一种方法便捷的多。

———————————————— 本篇完 ————————————————

看完之后,麻烦您顺手点击下方 “点赞” 两个字给我点个赞吧 ^-^ , 谢谢您了。

如果您还能像我小学一年级班主任好老师那样,给我随心写上几句表扬或批评的话语,那真是感激不尽!

在我人生的道路上,有了您的鼓励和指导,我一定成长快快。

Top