在这里咱们先学一个简单的知识点。python
li = ['alex', '银角', '女神', 'egon', '太白'] for i in enumerate(li): print(i) for index, name in enumerate(li, 1): print((index, name)) for index, name in enumerate(li, 100): # 起始位置默认是0,可更改 print((index, name)) ''' 输出的结果为: (0, 'alex') (1, '银角') (2, '女神') (3, 'egon') (4, '太白') (1, 'alex') (2, '银角') (3, '女神') (4, 'egon') (5, '太白') (100, 'alex') (101, '银角') (102, '女神') (103, 'egon') (104, '太白') '''
下面讲解闭包这个概念是参考博客太白金星。但愿你们有不懂的地方能够问我,能够共同讨论学习。安全
因为闭包这个概念比较难以理解,尤为是初学者来讲,相对难以掌握,因此咱们经过示例去理解学习闭包。网络
给你们提个需求,而后用函数去实现:完成一个计算不断增长的系列值的平均值的需求。闭包
例如:整个历史中的某个商品的平均收盘价。什么叫平局收盘价呢?就是从这个商品一出现开始,天天记录当天价格,而后计算他的平均值:平均值要考虑直至目前为止全部的价格。app
好比大众推出了一款新车:小白轿车。函数
第一天价格为:100000元,平均收盘价:100000元工具
次日价格为:110000元,平均收盘价:(100000 + 110000)/2 元学习
第三天价格为:120000元,平均收盘价:(100000 + 110000 + 120000)/3 元优化
........设计
series = [] def make_averager(new_value): series.append(new_value) total = sum(series) return total / len(series) print(make_averager(100000)) print(make_averager(110000)) print(make_averager(120000))
从上面的例子能够看出,基本上完成了咱们的要求,可是这个代码相对来讲是不安全的,由于你的这个series列表是一个全局变量,只要是全局做用域的任何地方,均可能对这个列表进行改变。
series = [] def make_averager(new_value): series.append(new_value) total = sum(series) return total / len(series) print(make_averager(100000)) print(make_averager(110000)) series.append(666) # 若是对数据进行相应改变,那么你的平均收盘价就会出现很大的问题,数据没有安全性。 print(make_averager(120000))
那么怎么办呢?有人说,你把他放在函数中不就好了,这样不就是局部变量了么?数据不就相对安全了么?
def make_averager(new_value): series = [] series.append(new_value) total = sum(series) return total / len(series) print(make_averager(100000)) # 100000.0 print(make_averager(110000)) # 110000.0 print(make_averager(120000)) # 120000.0
这样计算的结果是不正确的,那是由于执行函数,会开启一个临时的名称空间,随着函数的结束而消失,因此你每次执行函数的时候,都是从新建立这个列表,那么这怎么作呢?这种状况下,就须要用到咱们讲的闭包了,咱们用闭包的思想改一下这个代码。
def make_averager(): series = [] def averager(new_value): series.append(new_value) total = sum(series) return total/len(series) return averager avg = make_averager() print(avg(100000)) print(avg(110000)) print(avg(120000))
你们仔细看一下这个代码,我是在函数中嵌套了一个函数。那么avg 这个变量接收的实际是averager函数名,也就是其对应的内存地址,我执行了三次avg 也就是执行了三次averager这个函数。那么此时大家有什么问题?
确定有学生就会问,那么个人make_averager这个函数只是执行了一次,为何series这个列表没有消失?反而还能够被调用三次呢?这个就是最关键的地方,也是闭包的精华所在。我给你们说一下这个原理,以图为证:
上面被红色方框框起来的区域就是闭包,被蓝色圈起来的那个变量应该是make_averager()函数的局部变量,它应该是随着make_averager()函数的执行结束以后而消失。可是他没有,是由于此区域造成了闭包,series变量就变成了一个叫自由变量的东西,averager函数的做用域会延伸到包含自由变量series的绑定。也就是说,每次我调用avg对应的averager函数 时,均可以引用到这个自用变量series,这个就是闭包。
闭包的定义:
闭包是嵌套在函数中的函数。
闭包必须是内层函数对外层函数的变量(非全局变量)的引用。
如何判断判断闭包?举例让同窗回答:
# 例一: def wrapper(): a = 1 def inner(): print(a) return inner ret = wrapper() # 例二: a = 2 def wrapper(): def inner(): print(a) return inner ret = wrapper() # 例三: def wrapper(a,b): def inner(): print(a) print(b) return inner a = 2 b = 3 ret = wrapper(a,b)
以上三个例子,最难判断的是第三个,其实第三个也是闭包,若是咱们每次去研究代码判断其是否是闭包,有一些不科学,或者过于麻烦了,那么有一些函数的属性是能够获取到此函数是否拥有自由变量的,若是此函数拥有自由变量,那么就能够侧面证实其是不是闭包函数了(了解):
def make_averager(): series = [] def averager(new_value): series.append(new_value) total = sum(series) return total/len(series) return averager avg = make_averager() # 函数名.__code__.co_freevars 查看函数的自由变量 print(avg.__code__.co_freevars) # ('series',) 固然还有一些参数,仅供了解: # 函数名.__code__.co_freevars 查看函数的自由变量 print(avg.__code__.co_freevars) # ('series',) # 函数名.__code__.co_varnames 查看函数的局部变量 print(avg.__code__.co_varnames) # ('new_value', 'total') # 函数名.__closure__ 获取具体的自由变量对象,也就是cell对象。 # (<cell at 0x0000020070CB7618: int object at 0x000000005CA08090>,) # cell_contents 自由变量具体的值 print(avg.__closure__[0].cell_contents) # []
闭包的做用:保存局部信息不被销毁,保证数据的安全性(这是一个十分重要的做用)。
闭包的应用:
在使用装饰器以前,咱们先开始了解一下开发封闭原则。那么什么是开发封闭原则呢?
# 开放封闭原则 # 开放:对代码的拓展是开放的。更新地图,加新枪,等等 # 封闭:对源码的修改是封闭的。闪躲用q。就是一个功能,一个函数。 # 别人用赤手空拳打你,用机枪扫你,扔雷.....这个功能不会改变。
1.对扩展是开放的
咱们说,任何一个程序,不可能在设计之初就已经想好了全部的功能而且将来不作任何更新和修改。因此咱们必须容许代码扩展、添加新功能。
2.对修改是封闭的
就像咱们刚刚提到的,由于咱们写的一个函数,颇有可能已经交付给其余人使用了,若是这个时候咱们对函数内部进行修改,或者修改了函数的调用方式,颇有可能影响其余已经在使用该函数的用户。
因此装饰器最终最完美的定义就是:在不改变原被装饰的函数的源代码以及调用方式下,为其添加额外的功能。
那么装饰器是什么呢?
# 什么叫作装饰器 # 装饰器:装饰,装修,房子原本能够住,若是装修,不影响你住 # 并且体验更加,让你的生活中增长了许多的功能,例如看电视,洗澡 # 器:工具 # 装饰器:彻底遵循开放封闭原则。 # 装饰器:在不改变原函数的代码以及调用方式的前提下,为其增长新的功能
代码优化:语法糖
根据个人学习,咱们知道了,若是想要各给一个函数加一个装饰器应该是这样:
def home(name,age): time.sleep(3) # 模拟一下网络延迟以及代码的效率 print(name,age) print(f'欢迎访问{name}主页') def timer(func): # func = home def inner(*args,**kwargs): start_time = time.time() func(*args,**kwargs) end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') return inner home = timer(home) home('太白',18)
若是你想给home加上装饰器,每次执行home以前你要写上一句:home = timer(home)这样你在执行home函数 home('太白',18) 才是真生的添加了额外的功能。可是每次写这一句也是很麻烦。因此,Python给咱们提供了一个简化机制,用一个很简单的符号去代替这一句话。
def timer(func): # func = home def inner(*args,**kwargs): start_time = time.time() func(*args,**kwargs) end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') return inner @timer # home = timer(home) def home(name,age): time.sleep(3) # 模拟一下网络延迟以及代码的效率 print(name,age) print(f'欢迎访问{name}主页') home('太白',18)
你看此时我调整了一下位置,你要是不把装饰器放在上面,timer是找不到的。home函数若是想要加上装饰器那么你就在home函数上面加上@home,就等同于那句话 home = timer(home)。这么作没有什么特殊意义,就是让其更简单化,好比你在影视片中见过野战军的做战时因为不方便说话,用一些简单的手势表明一些话语,就是这个意思。
至此标准版的装饰器就是这个样子:
def wrapper(func): def inner(*args,**kwargs): '''执行被装饰函数以前的操做''' ret = func(*args,**kwargs) '''执行被装饰函数以后的操做''' return ret return inner
这个就是标准的装饰器,彻底符合代码开放封闭原则。这几行代码必定要背过,会用。