闭包并不是 Python 独有的概念。许多其他编程语言共享相同的概念。虽然很多初学者可能听说过它,但他们并不确切知道它是什么以及如何使用它。在本文中,我重点介绍了有关 Python 闭包的最基本知识,希望你能更好地理解这个概念。 一、内外功能如前所述,闭包存在于许多编程语言中,以下定义取自百度百科:
这个定义对很多人来说太技术化了,而且它并不特定于 Python。为了帮助你理解这个概念,这里有一个简单的版本:闭包是在外部函数中创建并使用外部函数变量的内部函数。它由外部函数作为其输出值返回。还是太技术了?我认同。最好通过一个真实的例子来理解。我们先看下面的代码片段: def multiplier_creator(n): def multiplier(number): return number * n return multiplierdouble_multiplier = multiplier_creator(2)triple_multiplier = multiplier_creator(3) 在上面的代码中,double_multiplier和triple_multiplier是两个闭包,因为它们满足闭包的定义,如下所述。
非局部变量的绑定可能是关于闭包最令人困惑的部分。让我们在下一节中探讨它。 2.局部变量和非局部变量当我们使用函数时,我们知道在函数中,我们可以自由使用任何传递的参数,这些参数称为局部变量。本质上,函数形成了一个局部作用域,它限制了对其变量的访问。 就上面的例子而言,multiplier_creator函数定义了一个局部作用域,参数n是一个局部变量。在类似的函数中,内部函数multiplier定义了另一个局部作用域,参数number是局部变量。但值得注意的是,该multipler函数还使用参数n。虽然n是multiplier_creator的作用域的局部变量,但它不是multipler函数的局部变量。因此,在示例中,乘数函数使用非局部变量,也称为自由变量。 除了局部变量和非局部变量的区别,你们中的一些人可能还听说过全局变量,它们是在模块级别定义的变量。 你可能已经注意到,从内部函数的角度来看,我们将访问外部函数的局部变量的过程称为非局部变量绑定。用绑定来描述这个特性是很有意义的。绑定到底是什么意思?让我们在下一节中学习它。 3. 非局部变量绑定非局部变量的绑定在其他一些语言中也称为非局部变量捕获,以描述闭包的特性。你可以简单地将其概念化为内部函数“拥有”使用的非局部变量。让我们观察以下特征: >>> del multiplier_creator >>> double_multiplier(5) 10 >>> triple_multiplier(5) 15 在上面的代码中,我们删除了外部函数multiplier_creator,这样这个函数就变得不可访问了。但请注意闭包(即double_multiplier和triple_multiplier)使用multiplier_creator的参数n。因此,有些人可能认为关闭将停止工作。然而,他们仍在产生预期的结果。 根本原因是闭包已经建立了它使用的非局部变量的绑定。换句话说,它有一个绑定变量的副本。为了观察这个特性,考虑一些特殊的检查函数,如下所示: >>> double_multiplier.__code__.co_freevars('n',)>>> double_multiplier.__closure__[0].cell_contents2>>> triple_multiplier.__code__.co_freevars('n',)>>> triple_multiplier.__closure__[0].cell_contents3 __code__.co_freevars允许我们检查闭包的非局部变量绑定的名称,允许__closure__[0].cell_contents我们检查绑定的非局部变量的值。你不必知道这些功能的细节,它们只是底层实现。 重要的是要知道,因为闭包通过绑定拥有自己的非局部变量副本,所以即使外部函数已被删除,它们仍然可以工作。 4. Nonlocal 关键字和 Unboundlocalerror 异常人们在创建闭包时,有时会遇到UnboundLocalError异常。让我们在下面的代码片段中看到这一点: >>> def running_total_multiplier_creator(n):... running_total = 0... def multiplier(number):... product = number * n... running_total += product... return running_total... return multiplier... >>> running_doubler = running_total_multiplier_creator(2)>>> running_doubler(5)Traceback (most recent call last): File '<stdin>', line 1, in <module> File '<stdin>', line 5, in multiplierUnboundLocalError: local variable 'running_total' referenced before assignment 乍一看,该
如果我们退一步看看这个multiplier函数,你可能会意识到我们想要使用在函数作用域内running_total定义的变量,从函数的角度来看 >>> def running_total_multiplier_creator(n):... running_total = 0... def multiplier(number):... nonlocal running_total... product = number * n... running_total += product... return running_total... return multiplier... >>> running_doubler = running_total_multiplier_creator(2)>>> running_doubler(5)10 如你所见,我们只是简单地声明它running_total是一个非局部变量,这会指示 Python 在查找该running_total变量时绕过局部作用域。 5. 为什么要闭包?到目前为止,我们已经回顾了什么是闭包,但你可能想知道我们为什么要麻烦拥有闭包特性?闭包最常见的应用之一是创建装饰函数。尽管你可能不知道闭包,但我敢打赌你可能听说过装饰器。在 Python 中,装饰器是在不影响装饰函数算法的情况下修改其他函数行为的函数。如果你想更深入地了解闭包,请参阅我关于装饰器的文章。 在这里,我将简要概述与我们刚刚学习的闭包相关的装饰器。以下代码向你展示了一个示例: def simple_logger(func): def decorated(*args, **kwargs): print(f'You're about to call {func}') result = func(*args, **kwargs) print(f'You just called {func}') return result return decorated@simple_loggerdef hello_world(): print('Hello, World!') 这simple_logger是一个装饰器,使用它涉及一个@符号作为其名称的前缀和装饰函数上方的位置。当你调用该hello_world函数时,将发生以下情况: >>> hello_world()You're about to call <function hello_world at 0x101ce9790>Hello, World!You just called <function hello_world at 0x101ce9790> 这个特性背后的原因是装饰函数实际上是一个闭包。装饰过程在幕后有以下两个步骤: # 第一步def hello_world(): print('Hello, World!')# 第二步 hello_world = simple_logger(hello_world) 为了证明这hello_world确实是一个闭包,我们可以像之前一样运行以下检查。 >>> hello_world.__code__.co_freevars('func',)>>> hello_world.__closure__[0].cell_contents<function hello_world at 0x101ce9790>
结论在本文中,我们回顾了闭包的五个最重要的方面。以下是对这些要点的快速回顾:
|
|