Python:函数式编程(三):闭包

闭包的定义:

维基百科关于闭包的定义:

在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。

前面提到函数式编程中函数作为“一等公民”,因此一个函数可以作为另一个函数的返回值。这就很显然的引出的嵌套函数。
在嵌套函数中,最内部函数的变量就是局部变量,而定义在模块最外层的变量就是全局变量,是在全局可见的,而既不是局部变量也不是全局变量的变量就是自由变量,例如外部函数中的变量:

g = 10


def outer():
    msg_out = 'Outer'
    print(g)

    def inter():
        msg_in = 'Inter'
        print(g)

    return inter

比如这里的 msg_in 就是一个局部变量,g 是一个全局变量,msg_out 是一个自由变量。
函数内部是可以访问到全局变量的,但是函数外并不能访问到局部变量。

a = 10


def f():
    b = 100
    print(a)


f()
print(b)

结果如下:
在这里插入图片描述
在嵌套函数中:

g = 10


def outer():
    msg_out = 'Outer'
    msg_2 = 'Msg_2'
    print(msg_2)

    def inter():
        msg_in = 'Inter'
        print(msg_in)
        print(msg_out)
        print('Inter',g)
    print('Outer', g)
    return inter


f1 = outer()
f1()

结果如下:
在这里插入图片描述
我们看到内部函数不仅可以访问到局部变量 msg_in 和全局变量 g,也可以访问到自由变量 msg_out。
这里有一个关键就是 inter 函数作为了外部函数的返回值。
注意一下最后两句:

f1 = outer()
f1()

一般情况下,函数中的局部变量仅在函数的执行期间可用,一旦 outer 执行过后,我们会认为 msg_out 变量将不再可用。然而,在这里我们发现 outer 执行完之后,在调用 f1 的时候 msg_out 变量的值正常输出了,这就是闭包的作用,闭包使得局部变量在函数外被访问成为可能。这里的 f1 就是一个闭包,闭包本质就是一个函数,闭包可以使本应该被回收的变量 msg_out 继续存在内存中。

闭包,顾名思义,就是一个封闭的包裹,里面包裹着自由变量,就像在类里面定义的属性值一样,自由变量的可见范围随同包裹,哪里可以访问到这个包裹,哪里就可以访问到这个自由变量。

所有函数都有一个 __closure__属性,如果这个函数是一个闭包的话,那么它返回的是一个由 cell 对象组成的元组对象。cell 对象的 cell_contents 属性就是这个闭包中的自由变量。我们可以使用闭包函数的:.__code__.co_freevars 和 inner.__closure__ 来返回自由变量的变量名和值:

def outer():
    msg_out = 'Outer'
    msg_2 = 'Msg_2'
    print(g)

    def inter():
        msg_in = 'Inter'
        print(msg_in)
        print(msg_out)
        print(msg_2)
        print(g)

    return inter


f1 = outer()
f1()

print(f1.__code__.co_freevars)
print(f1.__closure__[0].cell_contents, f1.__closure__[1].cell_contents)

结果如下:
在这里插入图片描述
这也就是为什么还可以继续使用的自由变量的原因。
我们对上面代码做一点小的修改:
删除 inter 里面的打印 msg_2 语句和修改最后一句只输出第一个自由变量,即:

g = 10


def outer():
    msg_out = 'Outer'
    msg_2 = 'Msg_2'
    print(g)

    def inter():
        msg_in = 'Inter'
        print(msg_in)
        print(msg_out)
        print(g)

    return inter


f1 = outer()
f1()

print(f1.__code__.co_freevars)
print(f1.__closure__[0].cell_contents)

结果如下:
在这里插入图片描述
我们如果想要打印出 f1.__closure__[1].cell_contents 会报错,这是因为我们内部函数如果不使用外部函数的变量,也就没必要保存起来。也就是说并不是所有的外部函数的变量都会存下来,只有内部函数需要的,这样可以尽量节省内存。

闭包的特点:

函数作为返回值

我们返回的是一个函数,也就是 f1 实际上是 inter 函数:

print(f1.__name__)

结果如下:
在这里插入图片描述

每次返回一个新的函数

闭包还有一个特点:每次返回的都是一个新的函数:

f1 = outer()
f2 = outer()
print(f1 == f2)

结果如下:
在这里插入图片描述

闭包返回的函数不会立即执行

g = 10


def outer():
    msg_out = 'Outer'
    msg_2 = 'Msg_2'
    print(msg_2)

    def inter():
        msg_in = 'Inter'
        print(msg_in)
        print(msg_out)
        print(g)
    print(g)
    return inter


f1 = outer()

结果如下:
在这里插入图片描述
即使输出了 g = 10 之后也没有执行 inter 里面的代码,我们需要运行 f1() 才会执行;

g = 10


def outer():
    msg_out = 'Outer'
    msg_2 = 'Msg_2'
    print(msg_2)

    def inter():
        msg_in = 'Inter'
        print(msg_in)
        print(msg_out)
        print(g)
    print(g)
    return inter


f1 = outer()
f1()

结果如下:
在这里插入图片描述
这也使得我们在返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
否则就会:

def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i * i
        fs.append(f)
    return fs


f1, f2, f3 = count()
print(f1())
print(f2())
print(f3())

一般觉得结果会是 1,4,9。但其实结果如下:
在这里插入图片描述
原因就是:返回的函数引用了变量 i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量 i 已经变成了 3,这个时候再执行,因此最终结果为9。

闭包的使用:

函数工厂

闭包可以用来构建一个函数工厂,可以制造出一类类似但不同的函数:

def fun_out(action1):
    def fun_in(object1):
        print(action1, object1)
    return fun_in


play = fun_out('Play')
play('Basketball')
play('Football')

buy = fun_out('Buy')
buy('T-shirt')
buy('Shoes')

结果如下:
在这里插入图片描述
这有点类似于一个函数的加工厂~

装饰器

后续介绍~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值