闭包
由于闭包这个概念比较难以理解,尤其是初学者来说,相对难以掌握,所以我们通过示例去理解学习闭包。
给大家提个需求,然后用函数去实现:完成一个计算不断增加的系列值的平均值的需求。
例如:整个历史中的某个商品的平均收盘价。什么叫平局收盘价呢?就是从这个商品一出现开始,每天记录当天价格,然后计算他的平均值:平均值要考虑直至目前为止所有的价格。
比如大众推出了一款新车:小白轿车。
第一天价格为: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) # []
闭包的定义:
- 闭包是嵌套在函数中的函数。
- 闭包必须是内层函数对外层函数的变量(非全局变量)的引用。
闭包的作用:保存局部信息不被销毁,保证数据的安全性。
闭包的应用:
- 可以保存一些非全局变量但是不易被销毁、改变的数据。
- 装饰器。
闭包主要应用于装饰器
装饰器
开放封闭原则:
1.对扩展开放 -- 支持增加新功能
2.对修改源代码是封闭,对调用方式是封闭的
装饰:在原有的基础上额外添加功能
语法糖必须要放在被装饰的函数正上方
# import time
# def run_time(f): #index 传到f 这里,系统写了一个f = index 代表这index的内存地址
所以底下f()是index() 还有装饰器本质就是闭包!!!!
# def inner():
# strat_time = time.time() # 被装饰函数之前
# f()
# print(time.time() - strat_time) # 被装饰函数之后
# return inner # 不能加括号
#@run_time #语法糖的本质就是 index=run_time(index)
#def index():
print("is index")
#
@run_time
#def func():
print("is func")
#index()
#func()
标准版装饰器
def wrapper(func):
def inner(* args,**kwargs)
"""执行被装饰函数前的操作“”
func(*args,**kwargs)
"""执行被装饰函数后的操作“”“
return inner
@wrapper
def index():
print("is index")
index()
装饰器传参
def log_time(func):
def make_decorater(*args, **kwargs): # 接受调用语句的实参,在下面传递给被装饰函数(原函数)
print('现在开始装饰')
test_func = func(*args, **kwargs) # 如果在这里return,则下面的代码无法执行,所以引用并在下面返回
print('现在结束装饰')
return test_func # 因为被装饰函数里有return,所以需要给调用语句(test(2))一个返回,又因为test_func = func(*args,**kwargs)已经调用了被装饰函数,这里就不用带()调用了,区别在于运行顺序的不同。
return make_decorater
@log_time #test=log_time(test)=make_decorater
def test(num):
print('我是被装饰的函数')
return num + 1
a = test(2) # test(2)=make_decorater(2) 2传到make_decorater 所以必须写*arg接收参数
print(a)
带参数的装饰器
def auth(x):
def auth2(func):
def inner(*args, **kwargs):
if login_status['status']:
ret = func()
return ret
if x == 'wechat':
username = input('请输入用户名:').strip()
password = input('请输入密码:').strip()
if username == '太白' and password == '123':
login_status['status'] = True
ret = func()
return ret
elif x == 'qq':
username = input('请输入用户名:').strip()
password = input('请输入密码:').strip()
if username == '太白' and password == '123':
login_status['status'] = True
ret = func()
return ret
return inner
return auth2
@auth('wechat')
def jitter():
print('记录美好生活')
@auth('qq')
def pipefish():
print('期待你的内涵神评论')
@auth('wechat') :分两步:
第一步先执行auth('wechat')函数,得到返回值auth2
第二步@与auth2结合,形成装饰器@auth2 然后在依次执行。
这样就是带参数的装饰器,参数可以传入多个,一般带参数的装饰器在以后的工作中都是给你提供的, 你会用就行,但是自己也一定要会写,面试经常会遇到。
装饰器装饰多个函数
def wrapper1(func):
def inner1(*args,**kwargs):
print("这是装饰器一开始")
func(*args,**kwargs)
print("这是装饰器一结束")
return inner1
def wrapper2(func):
def inner2(*args,**kwargs):
print("这是装饰器二开始")
func(*args,**kwargs)
print("这是装饰器二结束")
return inner2
@wrapper1 #func = wrapper1(func)=inner1
@wrapper2 #func = wrapper2(func) = inner2
def func():
print("这是被装饰的函数")
func()
Python规定多个装饰器装饰一个函数的时候先执行离被装饰的函数最近的装饰器
执行流程:
首先走wrapper2 最近的装饰器,语法糖wrapper2的操作用代码表示是 func = wrapper2(func) = inner2
此时 func=inner2 然后再执行wrapper1 语法糖wrapper1的操作用代码表示func = wrapper1(func)=inner1
但是wrapper1中func已经是inner2了 所以代码是func = wrapper1(inner2)=inner1
最后func()调用,先执行inner1() 先打印了这是装饰器一开始,然后执行inner里的func(),但是wrapper中func已经是inner2所以执行inner2函数,打印了这是装饰器二开始,到ineer2函数中func()执行时,此时的func是wrapper2的语法糖中的func,所以正常执行func(),打印了这是被装饰函数,然后接这执行,打印了这是装饰器二结束,由于是inner1中的func()调用的,所以返回去,继续执行print("这是装饰器一结束“),所以整个流程的结果:
这是装饰器一开始
这是装饰器二开始
这是被装饰的函数
这是装饰器二结束
这是装饰器一结束