【闭包】
在函数嵌套的前提下,内层函数引用了外层函数的变量(包括参数),外层函数又把内层函数当做返回值进行返回。这个“内层函数+所引用的外层变量”,称为闭包。
从这段话中,可以解析出构成闭包的三个条件:
1、形成函数嵌套;
2、内层函数引用外层函数的变量(包括参数);
3、外层函数的返回值是内层函数。
一个最简单的实例:
def test(): #外层函数
a = 1 #外层函数的变量
def test2(): #内层函数
print(a) #内层函数引用外层函数的变量
return test2 #外层函数的返回值是内层函数
则在调用外层函数 test 的时候,它的返回值是一个函数 test2,需要用一个变量“func”接收一下,即 func = test() ,此时,func接收到的就是test的返回值---test2,那么,将func 执行,就会间接调用到test2函数,将test里面的变量 a的值打印出来。程序和运行结果如下:
def test():
a = 1
def test2():
print(a)
return test2
func = test()
func()
运行结果:
1
Process finished with exit code 0
【装饰器】
作用:在函数名以及函数体不改变的前提下,给一个函数附加一些额外的代码
比如现有如下需求:有一个函数 print1:它的作用是打印“123”,想要在不改变函数体的前提下,给它增加一个功能:在打印“123”之前打印一条线。
def print1():
print("123")
打印线的函数为 printLine
def printLine():
print("-" * 20)
现在,只能想办法在printLine函数里面执行print1,如下(此处,printLine完成如下功能:printLine接收一个参数func,先打印一条线,再把接收过来的函数执行一遍,此处没有直接在printLine里面直接执行print1,那样就写死了,如果后面还有需求:在别的函数前面也打印一条线,写死就会降低代码的复用性)
def printLine(func):
print("-" * 20)
func()
现在这样写:
printLine(print1)
先写个printLine,我们把print1当做参数传递到printLine里面,去调用这个函数。但是这样做有个问题,就是在这里,由于printLine函数的作用,在调用printLine函数的时候,已经执行了print1。而print1应该在业务逻辑代码里面执行的。
这时,需要借助一个之前的工具-闭包,把print1变成拥有printLine函数体的一个函数就行,而不要直接去执行printLine。也就是起到这样的效果:“print1 = printLine(print1)”因此:
def printLine(func):
def inner():
print("-" * 20)
func()
return inner
def print1():
print("123")
print1 = printLine(print1)
在printLine函数里面,重新定义了一个函数inner,inner有这样的功能:先打印一条线,再把传进printLine来的函数func执行一遍。最后,printLine把inner当做返回值返回,这样,print1就能接收到返回来的inner。print1指向了inner,而这个inner是printLine生成的,printLine做的事情就是,把inner这个闭包返回给了外界。
此时再执行函数print1:
# 业务逻辑代码
print1()
得到如下结果:
--------------------
123
Process finished with exit code 0
这里,就借助了闭包这样的特性,针对于函数print1,给它额外地装饰了一些功能,并且并没有改变print1函数里面的内容。而完成这一创举的,就是函数printLine,我们把printLine称作是一个“装饰器”。这样的操作,称作是“装饰器”这样的设计模式。
现在,如果还有其它函数有这样的装饰需求,比如在这两个打印内容前面打印一条线:
def print2():
print("abc")
def print3():
print("我爱中国")
有了这样一个装饰器,我们只需要像上面的一样,写成这样:
def printLine(func):
def inner():
print("-" * 20)
func()
return inner
def print1():
print("123")
print1 = printLine(print1)
def print2():
print("abc")
print2 = printLine(print2)
def print3():
print("我爱中国")
print3 = printLine(print3)
# 业务逻辑代码
print1()
print2()
print3()
就可以实现:
--------------------
123
--------------------
abc
--------------------
我爱中国
Process finished with exit code 0
但是,这样写起来,不是那么好看。Python给我们提供了“语法糖”这样一种写法:(一种更加方便的方式)
def printLine(func):
def inner():
print("-" * 20)
func()
return inner
@printLine
def print1():
print("123")
# print1 = printLine(print1)
@printLine
def print2():
print("abc")
# print2 = printLine(print2)
@printLine
def print3():
print("我爱中国")
# print3 = printLine(print3)
# 业务逻辑代码
print1()
print2()
print3()
如上所示,不再需要以前的
print1 = printLine(print1)
print2 = printLine(print2)
print3 = printLine(print3)
只需要在各个函数上面装饰一句“@printLine”。这样一来,整个代码的颜值、格调就都上来啦!
【装饰器的几个注意事项】
1、装饰器的执行,是立即执行的,在装饰的时候就执行,而并不是等到调用被装饰的函数时才执行。也就是说,下面代码中,当执行到@printLine时,装饰器已经执行,而不是在业务逻辑代码中调用print3函数时才执行;
@printLine
def print3():
print("我爱中国")
# 业务逻辑代码
print3()
2、如果是多个装饰器叠加装饰一个对象,是“从上到下装饰,从外层到内层装饰”,而执行的时候,是从下到上,从内层到外层执行;
def printLine(func):
def inner():
print("-" * 20)
func()
return inner
def printStar(func):
def inner():
print("*" * 20)
func()
return inner
@printLine
@printStar
def print1():
print("123")
# 业务逻辑代码
print1()
结果如下:
--------------------
********************
123
Process finished with exit code 0
3、对有参函数进行装饰时,要保证函数调用参数个数一致,为了通用,可以使用不定长参数,结合拆包操作进行处理”;
def printLine(func):
def inner(*args, **kwargs):
print("-" * 20)
func(*args, **kwargs)
return inner
@printLine
def print1(num1, num2, num3):
print(num1, num2, num3)
@printLine
def print2(num):
print(num)
# 业务逻辑代码
print1(666, 999, num3=888)
print2(123)
得到结果:
--------------------
666 999 888
--------------------
123
Process finished with exit code 0
4、对有返回值的函数进行装饰,要保证函数返回值一致;
def printLine(func):
def inner(*args, **kwargs): # 为了通用,可变参数
print("-" * 20)
result = func(*args, **kwargs) # 拆包操作
return result # 返回的值就是被调用函数的执行结果
return inner
@printLine
def sum1(num1, num2, num3):
print(num1, num2, num3)
return num1 + num2 + num3 # 有返回值
@printLine
def sum2(num):
print(num) # 没有返回值
# 业务逻辑代码
result1 = sum1(147, 258, num3=369)
result2 = sum2(456)
print(result1, result2)
结果如下:
--------------------
147 258 369
--------------------
456
774 None
Process finished with exit code 0
4、带有参数的装饰器
# 根据不同的参数生成不同的装饰器
def getzhuangshiqi(char):
def zhuangshiqi(func):
def inner():
print(char * 20)
func()
return inner
return zhuangshiqi
@getzhuangshiqi("*")
@getzhuangshiqi("=")
def print1():
print("666")
# 业务逻辑代码
print1()
结果如下:
********************
====================
666
Process finished with exit code 0