1  什么是闭包

闭包(Closure)是函数式编程中的一个重要概念。在Python中,闭包可以简单理解为:一种特殊的函数。

这种函数由两个函数嵌套组成:外函数:嵌套函数中最外层的函数称之为外函数。内函数:嵌套函数中最内层的函数称之为内函数;内函数引用了外函数的临时变量;外函数的返回使用了临时变量的内函数。

2  闭包如何创建

在Python中,创建闭包非常简单。只需要定义一个外部函数,并在其中定义一个内部函数,内部函数引用外部函数的变量或参数,外部函数返回内部函数即可。

# 定义一个名为 outer_function 的外部函数,它接受一个参数 x

def outer_function(x):

# 在外部函数内部定义一个局部变量 y,并赋值为 5

y = 5

# 在 outer_function 内部定义了一个嵌套函数 inner_function,它接受一个参数 z

def inner_function(z):

# 返回 x、y 和 z 三个变量的和

# 注意:这里的 x 和 y 是来自外部函数 outer_function 的作用域(闭包)

return x + y + z

# outer_function 返回 inner_function 函数对象(而不是执行结果)

# 当 outer_function 被调用时,它会创建一个新的 inner_function 实例,

# 这个实例“记住”了当时 outer_function 作用域中的 x 和 y 的值

return inner_function

# 调用 outer_function 函数,传入参数 7,并将返回的 inner_function 赋值给 closure 变量

# 此时,closure 变量实际上是一个函数对象,它“记住”了 x=7 和 y=5

closure = outer_function(7)

# 调用 closure 函数(即之前保存的 inner_function 实例),传入参数 9

# 由于闭包的特性,尽管 outer_function 已经执行完毕,但 inner_function 仍然可以访问 x 和 y 的值

result = closure(9)

# 打印 result 的值,预期结果是 7(x的值)+ 5(y的值)+ 9(z的值)= 21

print(result) # 输出:21

这段代码展示了闭包的一个基本应用。闭包是指一个能访问和操作其外部词法环境(lexical environment)的函数。

在这个例子中,inner_function 是一个闭包,因为它访问了定义它的外部函数 outer_function 的作用域中的变量 x 和 y。即使 outer_function 的执行已经结束,inner_function 仍然保持着对 x 和 y 的引用,并且可以在需要时被调用。当 outer_function(7) 被调用时,它创建了一个新的环境,其中 x 被赋值为 7,并且在这个环境中定义了 inner_function。随后 outer_function 返回了 inner_function 的一个实例(或者说是一个“绑定”了特定环境的函数对象)。这个返回的函数对象被赋值给了 closure 变量。之后,当我们调用 closure(9) 时,实际上是调用了那个带有特定环境(x=7, y=5)的 inner_function 实例。闭包的特性使得 inner_function 仍然能够访问到 x 和 y 的值,并将它们与传入的参数 z=9 相加,最终返回结果 21。

3  闭包的作用

闭包的主要作用有两个:

可以记住并访问其所在的词法作用域。可以让函数的状态私有化。

通过闭包,我们可以创建出具有“记忆”功能的函数,这些函数可以记住之前调用时的状态,从而实现一些特殊的功能,如计数器、缓存等。

4  闭包与变量作用域

在Python中,变量的查找遵循LEGB规则,即:Local -> Enclosing -> Global -> Built-in(由内向外查找)。当在函数内部访问一个变量时,Python会首先在当前函数的局部作用域中查找,如果找不到,则会在包含(enclosing)这个函数的作用域中查找,以此类推,直到找到为止。如果在所有作用域中都找不到,Python会抛出一个NameError异常。

由于闭包可以记住并访问其所在的词法作用域,因此闭包中的内部函数在查找变量时,会先在其自身的局部作用域中查找,如果找不到,则会去其外部函数的词法作用域中查找。

5  闭包的应用场景

闭包在Python中有许多应用场景,以下是一个具体的例子:

5.1  实例一:计数器

假设我们需要一个函数,每次调用它时都会记住之前的调用次数,并返回当前的调用次数。这种需求可以通过闭包来实现:

def create_counter():

count = 0 # 这是一个外部变量

def counter():

nonlocal count # 声明我们要引用的是外部变量

count += 1 # 每次调用时,计数器加1

return count # 返回当前的计数

return counter # 返回内部函数,也就是闭包

# 创建一个计数器

my_counter = create_counter()

# 调用计数器几次

print(my_counter()) # 输出 1

print(my_counter()) # 输出 2

print(my_counter()) # 输出 3

在这个例子中,create_counter函数返回了一个闭包counter。这个闭包引用了外部变量count,并且每次调用时都会使count加1。由于闭包记住了其所在的词法作用域,即使create_counter函数的执行上下文已经结束,counter仍然可以访问和修改count。

5.2  为什么要nonlocal count 声明我们要引用的是外部变量?

如果没有 nonlocal count 声明,Python会抛出 UnboundLocalErrorcounter 异常,这是因为Python在函数内部访问变量 count 时,会首先在当前函数的局部作用域中查找,而count += 1 会被Python 认为是一个新的局部变量,而不会去访问外部变量count = 0,因为 count 在 counter 函数内部从未被赋值过,所以会出现 UnboundLocalError 异常。

而 nonlocal 关键字的作用,就是告诉Python解释器 counter 函数内部的 count 变量是对外部作用域中的 count 变量的引用,而不是创建一个新的局部变量。

6 闭包和内嵌函数

闭包和内嵌函数在编程中都是重要的概念,它们之间有一些相似之处,但也存在一些关键的区别。

6.1  相似之处

定义位置:闭包和内嵌函数都是在另一个函数的内部定义的。它们都位于一个外部函数的词法作用域内。访问权限:无论是闭包还是内嵌函数,它们都可以访问其外部函数的变量和参数。这意味着内部函数可以读取、修改(如果允许)和使用外部函数的数据。扩展功能:通过闭包和内嵌函数,都可以实现更复杂的编程模式,如函数工厂、函数记忆等。

6.2  关键区别

作用域:内嵌函数的作用域仅限于其外部函数内部。一旦外部函数执行完毕,内嵌函数就无法再访问外部函数的变量(除非这些变量被声明为全局变量或使用了特殊的技术如nonlocal)。而闭包则不同,即使其外部函数已经执行完毕,闭包仍然可以访问并操作其外部函数的变量,这是通过词法作用域实现的。返回和调用:内嵌函数可以作为普通函数被调用,但通常只在外部函数内部被调用。而闭包常常作为外部函数的返回值,可以在外部函数外部被调用和使用。这使得闭包成为一种创建可重用代码片段的有效方式,可以封装数据和操作,并作为高阶函数传递。生命周期:内嵌函数的生命周期通常与其外部函数绑定在一起,当外部函数执行完毕时,内嵌函数可能也会被销毁(除非被其他引用所持有)。而闭包由于其能够访问外部函数的变量,即使外部函数已经执行完毕,闭包仍然可以存在并被调用。

综上所述,闭包和内嵌函数都允许内部函数访问外部函数的变量和参数,但闭包更加强调函数对外部环境的记忆能力,并且可以在外部函数外部被调用和使用。而内嵌函数则更侧重于在外部函数内部定义和使用。

6  闭包的注意事项

虽然闭包非常强大和灵活,但在使用时也需要注意以下几点:

一般情况下,函数在运行结束后会将临时变量都释放给内存。但是闭包比较特殊,由于内函数中会用到外函数的临时变量,因此它会将外函数的临时变量与内函数捆绑在一起,在外函数运行结束后,使其临时变量会在后续中继续被内函数调用,因此在使用闭包时需要注意内存消耗和性能问题。如果闭包引用的对象很大或者很多,可能会导致内存占用过高。由于闭包可以隐藏和封装数据,因此在设计和使用闭包时需要注意数据的安全性和可靠性。如果闭包被恶意利用或误用,可能会导致数据泄露或损坏。由于闭包中的内部函数可以访问外部函数的变量,因此在设计和使用闭包时需要注意变量的生命周期和作用域。如果外部函数的变量在内部函数之后被修改或删除,可能会导致内部函数无法正常工作。

参考文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: