闭包函数

闭包函数

内部函数包含对外部作用域而非全局作用域名字的引用,该内部函数称为闭包函数。

由于有了作用域的关系,我们就不能拿到函数内部的变量和函数了。如果我们就是想拿怎么办呢?返回呀!

我们都知道函数内的变量我们要想在函数外部用,可以直接返回这个变量,那么如果我们想在函数外部调用函数内部的函数呢?

是不是直接就把这个函数的名字返回就好了?

闭包函数最常用的用法

def func():
    name = 'diege'
    def inner():
        print(name)
    return inner

f = func()
f()

简单剖析一下上面的代码流程
闭包函数

第一步:定义func变量开辟存放变量的内存空间和名字
第二步:调用func并赋值给f变量
第三步:创建name变量和值
第四步:返回值是func中的内部函数inner的引用
第五步:调用f函数(此时 f 拥有name变量和内部函数inner)
第六步:进入inner函数
第七步:print

例子2 可以尝试调试以下代码

def wrapper():
    money = 1000
    def func():
        name = 'diege'
        def inner():
            print(name,money)
        return inner
    return func

f = wrapper()
i = f()
i()

由于闭包这个概念比较难以理解,尤其是初学者来说,相对难以掌握,所以我们通过示例去理解学习闭包。

给大家提个需求,然后用函数去实现:完成一个计算不断增加的系列值的平均值的需求。

例如:整个历史中的某个商品的平均收盘价。什么叫平局收盘价呢?就是从这个商品一出现开始,每天记录当天价格,然后计算他的平均值:平均值要考虑直至目前为止所有的价格。

比如大众推出了一款新车:小白轿车。

第一天价格为: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 outer(x):
    def inner(y):
        nonlocal x
        x+=y
        return x
    return inner


a = outer(5)
print(a(1))
print(a(3))

开始执行108行
闭包函数

nonlocal关键字声明使用外层(非全局)变量
闭包函数

此时返回值为6
闭包函数

开始执行109行后 发现x为108行执行后的返回值
闭包函数

此时这个*变量为9了
闭包函数

那么我们再加个print(a(10))应该返回值就是14啦
闭包函数

由此可见,从上面的例子就能说明每次调用inner时候,使用的闭包变量x实际上是同一个。

闭包的作用:保存局部信息不被销毁,保证数据的安全性。

闭包的应用:

  1. 可以保存一些非全局变量但是不易被销毁、改变的数据。

  2. 装饰器。

上一篇:记录我的 python 学习历程-Day13 匿名函数、内置函数 II、闭包


下一篇:python 13 内置函数II 匿名函数 闭包