在本文中,我将重点介绍有关 Python 闭包的最基本知识,希望你能更好地理解这个概念。
如前所述,许多编程语言中都存在闭包,以下定义来自维基百科:
“在编程语言中,闭包,也是词法闭包或函数闭包,是一种在具有一流函数的语言中实现词法范围 名称绑定的技术。” ——维基百科对闭包的定义
这个定义对很多人来说太技术性了,而且它不是 Python 特有的。为了帮助你理解这个概念,这里有一个简单的版本:闭包是一个内部函数,它在外部函数中创建并使用外部函数的变量。
它由外部函数作为其输出值返回。还是太技术化了?我认同。最好通过一个真实的例子来理解。我们先来看下面的代码片段:
return multiplier
double_multiplier = multiplier_creator(2) triple_multiplier = multiplier_creator(3)
在上面的代码中:
double_multiplier和triple_multiplier是两个闭包。
因为它们满足闭包的定义,如下所述:
multipler是在所述内创建的内部功能multiplier_creator的功能。其被称为外功能,因为它是外部的功能multiplier。
内部函数通常被称为嵌套函数,因为它嵌套在另一个函数中。
创建的内部函数是外部函数的返回值。需要注意的是,multiplier_creator函数multiplier直接返回函数而不是函数的输出值multiplier。
内部函数multipler使用n变量,它是外部函数的参数multiplier_creator。内部函数对外部变量的访问也称为非局部变量绑定。
非局部变量的绑定可能是关于闭包最令人困惑的部分。让我们在下一节中探讨它。
当我们使用函数时,我们知道在函数内,我们可以自由使用任何传递的参数,这些参数被称为局部变量。本质上,该函数形成了一个局部作用域,它限制了对其变量的访问。
就上面的例子而言,multiplier_creator函数定义了一个局部作用域,参数n是一个局部变量。在一个类似的函数中,内部函数multiplier定义了另一个局部作用域,参数number是一个局部变量。
但值得注意的是,该multipler函数还使用了参数n。尽管n是multiplier_creator的作用域的局部变量,但它对于multipler函数来说是非局部的。因此,在示例中,乘数函数使用非局部变量,也称为自由变量。
除了局部变量和非局部变量的区别,你们中的一些人可能还听说过全局变量,它们是在模块级别定义的变量。一些相关术语包括全局作用域和内置作用域。有兴趣的读者可以参考我之前关于这个话题的文章。
你可能已经注意到,从内部函数的角度来看,我们将访问外部函数的局部变量的过程称为非局部变量绑定。使用绑定来描述此功能非常重要。绑定到底是什么意思?让我们在下一节中学习它。
非局部变量的绑定在其他一些语言中也称为非局部变量捕获来描述闭包的特性。您可以简单地将其概念化为内部函数“拥有”使用过的非局部变量。让我们观察以下特征:
>>>triple_multiplier(5)
15
在上面的代码中:
我们删除了外部函数multiplier_creator,使该函数变得不可访问。
但请注意:
闭包如:double_multiplier和triple_multiplier使用multiplier_creator的参数n。
因此,有些人可能认为关闭将停止工作。然而,他们仍在产生预期的结果。
根本原因是闭包已经建立了它使用的非局部变量的绑定。换句话说,它有一个绑定变量的副本。要观察此功能,请考虑一些特殊的检查功能。
如下所示:
>>> triple_multiplier.__code__.co_freevars (‘n’,)
>>> triple_multiplier.__closure__[0].cell_contents
3
将__code__.co_freevars允许我们检查外地变量封盖结合的名字。
__closure__[0].cell_contents让我们检查绑定的非本地变量的值。你不必了解这些函数的细节,它们只是底层实现。
重要的是要知道,因为闭包通过绑定拥有自己的非局部变量副本,即使外部函数已被删除,它们仍然可以工作。
让我们在以下代码片段中看到这一点:
… 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 multiplier UnboundLocalError:local variable’running_total’referenced before assignment
闭包——UnboundLocalError
这样看,该running_total_multiplier_creator函数似乎可以生成一个闭包。
但是,当我们使用生成的闭包时:
UnboundLocalError会引发异常。这个异常是什么意思?
如果你阅读 Traceback 消息,你会发现有问题的代码是running_total += product。
为什么会出现这样的错误?这是解释:
这行代码本质上被解释为running_total = running_total + product。
变量查找顺序称为 LEGB 规则,遵循本地 -> 封闭 -> 全局 -> 内置的顺序。当你running_total = xxx在内部函数中运行时。
你正在内部函数的局部范围内注册一个局部变量。
当Python继续运行赋值语句右侧的代码时,它running_total再次遇到该变量,因此Python开始查找该变量并在本地范围内找到它。
但是,它注意到这个变量还没有被赋值,因为它的赋值语句还没有完成它的执行。
这就是为什么错误消息说:
local variable ‘running_total’ referenced before assignment。
当变量在赋值之前被引用时,它被称为未绑定错误。
如果我们退后一步查看multiplier函数。
你可能会意识到我们想要使用running_total在running_total_multiplier_creator函数作用域内定义的变量,从multiplier函数的角度来看,它被称为封闭作用域。
为了让内部函数理解它running_total不是一个局部变量,我们必须通过使用nonlocal关键字来明确。
这是正确的版本:
… product = number * n
… running_total += product
… return running_total
… return multiplier
…
>>>running_doubler=running_total_multiplier_creator(2)
>>> running_doubler(5)
10
闭包 — UnboundLocalError 修复
如你所见,我们简单地声明它running_total是一个非局部变量,它指示Python在查找running_total变量时绕过局部作用域。
到目前为止,我们已经回顾了闭包是什么,但是你可能想知道为什么我们要使用闭包功能?闭包最常见的应用之一是创建装饰器函数。
尽管你可能不知道闭包,但你可能听说过装饰器。在Python中,装饰器是修改其他函数行为而不影响被装饰函数的算法的函数。
如果你想更深入地了解闭包,请参阅我关于装饰器的文章。
在这里,我将简要概述与我们刚刚学到的闭包相关的装饰器。
以下代码向你展示了一个示例:
print(f”You just called {func}”)
return result
return decorated
@simple_logger
def hello_world():
print(“Hello, World!”)
闭包 -> 装饰器
这simple_logger是一个装饰器,使用它@sign prefixing在其名称前面加上一个符号。
并将其放置在被装饰的函数之上。
当你调用该hello_world函数时,将发生以下情况:
>>> hello_world()
将要调⽤ <function hello_world at 0x101ce9790>
Hello, World!
刚刚在 0x101ce9790 处调⽤了 <function hello_world>
这个特性背后的原因是被装饰的函数实际上是一个闭包。
装修过程在幕后有以下两步:
# Step 2
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>
- 修饰函数hello_world有一个非局部变量func。
- 绑定值是一个函数。作为相关的一点,重要的是要知道函数只是 Python 中的常规对象,因此函数可以被视为其他数据模型变量(例如,列表和字典)。
在本文中,我们回顾了闭包的五个最重要的方面。以下是对这些要点的快速回顾:
- 内部功能和外部功能是有区别的。闭包是在外部函数中创建的内部函数。
- 闭包涉及非局部变量的绑定。
- 一个函数有它的局部作用域。当你使用在函数内创建的变量时,你使用的是局部变量。如果你使用在函数外部创建的变量,则你使用的是非局部变量。
- nonlocal关键字意味着变量应被视为非局部变量。通常,LEGB 规则适用于变量查找。但是,使用nonlocal关键字,Python 在查找变量时将被指示绕过局部作用域。
- 装饰是创建一个闭包来替换其原始函数声明的过程。每个装饰函数都是引擎盖下的闭包。
本文转载来自:https://betterprogramming.pub/5-essential-aspects-of-python-closures-494a04e7b65e
作者:Yong Cui
转载请注明:XAMPP中文组官网 » Python 闭包的基本概念知识