DAY 9. 闭包和装饰器
9.1 闭包
闭包就是内部函数对外部函数作用域内变量的引用
可以看出
- 闭包是针对函数的,还有两个函数,内部函数和外部函数
- 闭包是为了让内部函数引用外部函数作用域内的变量的
我们先写两个函数
def fun1():print("我是fun1")def fun2():print("我是fun2")
这样fun2就作为fun1的内部函数,此时在函数外部是无法调用Fun2的,因为
- fun2实际上相当于fun1的一个属性(方法),作用域是fun1的块作用域,全局作用域中无法找到,
- 函数内属性的生命周期是在函数运行期间,在fun1中只是定义了fun2,并没有调用它
为了让fun2跳出fun1的生命周期,我们需要返回fun2,这样在外部获取到的fun1的返回值就是fun2,这样调用fun1的返回值就是调用了fun2,如:
def fun1():print("我是fun1")def fun2():print("我是fun2")return fun2var = fun1()
var()
# 我是fun1
# 我是fun2
当然,这还不是一个闭包,闭包是引用了自由变量的函数,所谓自由变量可以理解为局部变量,如果fun2调用了fun1中的变量,那么fun2就是一个闭包了。如
def fun1(var1):def fun2():print(f"var1 = {var1}")return fun2var = fun1(1)
var() # var1 = 1
闭包的作用
闭包私有化了变量,实现了数据的封装,类似于面向对象
def fun1(obj):def fun2():obj[0] += 1print(obj)return fun2if __name__ == '__main__':mylist = [i for i in range(5)]var = fun1(mylist)var()var()var()# [1, 1, 2, 3, 4]# [2, 1, 2, 3, 4]# [3, 1, 2, 3, 4]
9.2 装饰器
闭包在python中有一个重要的用途就是装饰器,装饰器接受被装饰的函数作为参数并执行一次调用,装饰器的本质还是一个闭包
def func1(func):def func2():print("func2")return func()return func2@func1
def Demo():print("Demo")if __name__ == '__main__':Demo()# func2# Demo
- 首先,
@func1
是一颗语法糖,等价于func1(Demo)()
- 外部函数必须能接收一个参数,也只能接受一个参数,如果有多个参数,必须再套一个函数,因为在使用
@
语法糖时,会自动把被修饰函数作为参数传递给装饰器 - 内部函数必须返回被装饰函数的调用
运行流程:
- 把被修饰函数作为参数传递给装饰器,这时函数返回的是闭包函数func2
- 隐式地调用func2,相当于
func2()
,执行函数体,输出func2,这时函数返回值是func()
,返回的直接是被修饰函数的调用,相当于直接执行被修饰函数,输出Demo
相当于:
def func1(func):def func2():print("func2")return func()return func2# @func1
def Demo():print("Demo")if __name__ == '__main__':# s = Demo()# 先把被修饰函数作为参数传递给修饰器,这里的s就是func2s = func1(Demo)# 调用闭包函数s()print(s)# func2# Demo# <function func1.<locals>.func2 at 0x00000117F163AD90>
9.2.1 装饰器带参数
def func1(num):def func2(func):def func3():if num >10:print("大于10")else:print("小于10")return func()return func3return func2@func1(num=12)
def Demo():print("Demo")if __name__ == '__main__':Demo()b
执行流程
- 将装饰器的参数传递给第一层函数,并返回第二层函数func2
- 将被修饰函数作为参数传递给第二层函数func2,隐式调用func2,返回闭包函数
- 执行闭包函数,并返回被修饰函数的调用(执行被修饰函数)
9.2.2 被修饰函数带参数
如果被修饰函数带有参数,需要把参数传递给内层闭包函数,返回被修饰函数的调用时记得加参数
def func1(func):def func2(arg):arg += 1# 记得加参数return func(arg)return func2@func1
def Demo(arg):print(arg)if __name__ == '__main__':Demo(11) # 12
9.2.3 例
- 求斐波那契数列任意一项的值
import timedef code_time(func):'''修饰器,用来打印函数运行时间:param func: 被修饰函数:return: func'''start_time = time.time()def closer(*args,**kwargs):result = func(*args,**kwargs)codeTime = time.time() - start_timeprint(f"This code runs at:{codeTime}")return resultreturn closerdef _Fibonacci(n):if n <= 1:return 1else:return _Fibonacci(n-1) + _Fibonacci(n-2)@code_time
def Fibonacci(n):return _Fibonacci(n)if __name__ == '__main__':var = Fibonacci(40)print(var)# This code runs at:61.738335609436035# 165580141
发现代码效率非常低,输出第四十个值需要一分多钟,这是应为每计算一个值,需要计算前两个值,这里有很多重复的,如
10||-----------------|9 8
|--------| |--------|
8 7 7 67,8被重复计算多次
所以需要把已经计算过的储存起来,计算之前先判断有没有计算过,没计算过再计算,修改程序为:
import timedef code_time(func):'''修饰器,用来打印函数运行时间:param func::return:'''start_time = time.time()def closer(*args,**kwargs):result = func(*args,**kwargs)codeTime = time.time() - start_timeprint(f"This code runs at:{codeTime}")return resultreturn closer
resultList = {0:1,1:1}
def _Fibonacci(n):if n <= 1:return 1else:if n-1 in resultList:a = resultList[n-1]else:a = _Fibonacci(n-1)resultList[n-1] = aif n-2 in resultList:b = resultList[n-2]else:b = _Fibonacci(n-2)resultList[n-2] = breturn a + b@code_time
def Fibonacci(n):return _Fibonacci(n)if __name__ == '__main__':var = Fibonacci(40)print(var)# This code runs at:0.0# 165580141
速度快了很多,但重复的代码是不能忍受的,使用修饰器重新一下:
import timedef code_time(func):start_time = time.time()def closer(*args, **kwargs):result = func(*args, **kwargs)codeTime = time.time() - start_timeprint(f"This code runs at:{codeTime}")return resultreturn closerdef modify(func):catch = {0: 1, 1: 1}def closer(*args):if args not in catch:catch[args] = func(*args)return catch[args]return closer@modify
def _Fibonacci(n):if n <= 1:return 1else:return _Fibonacci(n - 1) + _Fibonacci(n - 2)@code_time
def Fibonacci(n):return _Fibonacci(n)if __name__ == '__main__':var = Fibonacci(40)print(var)
有20节楼梯,一次可以走1,2,3,4级,总共有多少种走法
from my_python_package import code_timedef Modify(c = None):if c == None:c = {}def modify(func):catch = cdef closer(*args):if args[0] not in catch:catch[args[0]] = func(*args)return catch[args[0]]return closerreturn modify@Modify()
def _Stairs(num, steps):count = 0if num == 0:count = 1elif num > 0:for step in steps:count += _Stairs(num-step,steps)return count@code_time
def Stairs(num,steps):count = _Stairs(num,steps)return countif __name__ == '__main__':num = 20steps = [step for step in range(1,5)]count = Stairs(num, steps)print(count)# Stairs runs at: 0.0 s# 283953
9.3 总结
-
闭包:内部函数调用了外部函数作用域内的变量
- 针对函数
- 要有自由变量(私有变量)
- 要点:内部函数要跳出外部函数的生命周期,需要外部函数把他return出来
-
装饰器:
- 基础:闭包
- 作用:不修改原来代码的基础上拓展原函数功能
- 用处:修改API功能,AOP编程
- 要点:@语法糖,函数执行顺序
-
参考链接
Python高级编程技巧(进阶)(已完结)
Python的闭包与装饰器