Python 闭包


2021年8月23日, Learn eTutorial
1831

在本 Python 教程中,您将学习有关闭包的所有知识,闭包是函数式编程中的重要工具之一。您将了解 Python 中的闭包是什么,它们如何实现以及使用闭包的好处。首先,您将了解嵌套函数和 nonlocal 变量,它们是实现闭包的基本构造。

嵌套函数与 nonlocal 变量

简单来说,嵌套函数是指定义在另一个函数内部的函数。内部定义的函数称为**内部函数**,而包含内部函数的函数称为**外部函数**。

下面是嵌套函数的一个示例

def OuterFunction(msg): 

                def Innerfunction():  
                                print(msg)
                InnerFunction() 
           
OuterFunction('Welcome to Learn eTutorials')  

在上面的代码片段中,我们定义了一个 OuterFunction,它接受一个文本或消息作为参数,这里是 msg。我们还在 OuterFunction 内部定义了另一个函数,并将其命名为 InnerFunctionInnerFunction 打印我们在 OuterFunction 中作为参数传递的 msg。最后,我们从 OuterFunction 的作用域调用了内部函数。本质上,OuterFunction 包含了 InnerFunction,因此也称为包含函数,而内部函数是外部函数的局部函数,因此也称为嵌套函数。

上述代码的调用顺序总是从主函数开始,它调用 OuterFunction(‘Learn eTutorial!!!’),这将控制权转移到函数 OuterFunction(msg),在那里它会遇到函数调用 InnerFunction()。函数调用顺序可以最好地可视化如下

Calling Sequence of nested function

嵌套函数调用顺序

当一个函数在另一个函数内部声明时,就会发生函数嵌套。嵌套可以进行到任何级别,但增加级别会导致复杂性增加。

Non Local 变量

在上面的代码中,“msg”是唯一声明的变量。根据我们之前所知,变量可以根据其在函数或程序中的作用域和生命周期,在全局或局部声明。生命周期贯穿整个程序的变量称为全局变量,作用域仅限于函数的变量称为局部变量。但存在第三种变体,称为 nonlocal 变量,它主要与 Python 中的嵌套函数一起使用。

Non Local Variables

当一个变量在包含函数中定义时,它对嵌套函数来说具有 nonlocal 作用域。这些类型的变量称为 nonlocal 变量。Nonlocal 变量可以被定义它的函数及其所有嵌套函数访问。

对于前面的代码片段,nonlocal 变量的作用域可以最好地可视化如下

Non Local Variables

在此,变量 msg 是此程序中唯一声明的变量。 msg 是全局变量还是局部变量,还是 nonlocal 变量?

  • msg 不是全局变量,因为它不是在主函数中声明的,也没有 global 关键字。
  • 由于 msg 是在包含函数 OuterFunction() 中声明的,因此 'msg' 是其包含函数的局部变量。
  • 但是,您可以看到 msg 可以被嵌套函数 InnerFunction() 访问。那么我们能声称 'msg' 是局部变量吗?答案是 NO。这里 'msg' 对其嵌套函数来说是一个 nonlocal 变量。

现在让我们通过对前面的代码片段进行一些更改,看看如何区分局部变量和 nonlocal 变量。

示例:局部变量与 NonLocal 变量

def OuterFunction(): 
    msg = 'Welcome to Learn eTutorials' #Local Variable

    def InnerFunction(): # Nested Function
      
        msg = 'PYTHON' # Non Local Variable
        print('Inner: ',msg)

    InnerFunction() 
    print('Outer: ',x)           

OuterFunction() # Main Function 

考虑到包含函数的局部变量对其内部函数是 nonlocal 的这一事实,在内部函数中对 'msg' 所做的任何更改都不会反映在外部函数中的 'msg'。

输出

Inner:  PYTHON
Outer:  Welcome to Learn eTutorials

从上面的输出可以清楚地看出,函数 InnerFunction() 可以访问变量 'msg',但不能更改它们。内部函数只是用新值重新分配了该变量。这意味着在嵌套函数中已创建了一个同名的新局部变量。

如何修改 nonlocal 变量?

要修改 nonlocal 变量,我们必须使用 nonlocal 关键字显式声明它。通过这种方式,我们可以轻松地识别 nonlocal 变量。让我们看看 nonlocal 关键字的意义以及如果使用它们,我们以前的代码会发生什么变化。

示例:NonLocal 变量

def OuterFunction(): #Enclosed Function
    msg = 'Welcome to Learn eTutorials' #Local Variable

    def InnerFunction(): # Nested Function

        nonlocal msg 
        msg = 'PYTHON' # Non Local Variable
        print('Inner: ',msg)

    InnerFunction() 
              
OuterFunction() # Main Function 

在此示例中,我们在内部函数 InnerFunction() 中使用 non-local 关键字显式声明了 msg,并为其分配了一个字符串值。在嵌套函数中对 msg 所做的修改也反映在包含函数中。

输出

Inner:  PYTHON
Outer:  PYTHON

从输出可以清楚地看出,变量 msg 既不是全局变量,也不是局部变量。

Python 中的闭包

现在我们可以学习 Python 中的闭包了。为此,我们可以考虑前面的嵌套函数示例,看看如何将嵌套函数转换为闭包。

为了将嵌套函数转换为闭包,我们只需**返回不带括号的嵌套函数**。闭包函数结构如下所示。

Python Closure Strucuture

闭包结构

当一个函数的返回值取决于在函数外部声明的一个或多个变量的值时,则该函数称为闭包函数。在上面的示例中,InnerFunction 的返回值取决于在 InnerFunction 外部声明的 'msg' 变量。

闭包的重要特性

闭包将数据与代码绑定

Python 中闭包的首要也是最重要的特性是闭包将数据与代码封装在一起。闭包这个名字背后的事实是它将 nonlocal 数据绑定到内部函数代码中。

在上面的代码中,return InnerFunction 语句不仅仅是返回任何值;相反,当调用 OuterFunction 时,它返回函数本身。因此,需要声明变量来存储这个函数。

在我们的示例中,我们声明了一个新变量 get,它存储了 OuterFunction 返回的 InnerFunction。由于 get 包含函数,我们可以使用 get 作为函数,并像 get() 一样调用该函数。由于 InnerFunction 没有任何参数,我们不需要在 get() 中传递任何参数。

示例:闭包

def OuterFunction(msg): 
  
    def InnerFunction():  
        print('Inner: ',msg)
    return InnerFunction
              
get = OuterFunction('Welcome to Learn eTutorials') 

print(get)

print(get()) 

现在,当我们运行上面的代码时,我们会得到以下输出

输出

.InnerFunction at 0x01DF6148>
Inner:  Welcome to Learn eTutorials
None

从输出可以清楚地看出,get() 可以作为函数工作,并打印绑定到代码的数据。

闭包会记住其上下文

闭包可以定义为函数对象,它会记住封闭作用域中的 nonlocal 变量,无论该变量在内存中是否存在。

示例:闭包

def OuterFunction(msg): 
  
    def InnerFunction():  
        print('Inner: ',msg)
    return InnerFunction
              
get = OuterFunction('Welcome to Learn eTutorials') 

del OuterFunction
OuterFunction('Welcome to Learn eTutorials') 

在这段代码中,OuterFunction 是包含作用域,它使用 del 关键字被删除。删除后,我们再次尝试调用 OuterFunction。输出会是什么?

显然,Python 编译器会引发一个 Name Error,说明 OuterFunction 未定义,如下所示。

输出错误

OuterFunction('Welcome to Learn eTutorials')
NameError: name 'OuterFunction' is not defined

这时就体现了闭包强大的记忆能力。

示例:闭包

def OuterFunction(msg): 
  
    def InnerFunction():  
        print('Inner: ',msg)
    return InnerFunction
              
get = OuterFunction('Welcome to Learn eTutorials') 

del OuterFunction
get() 

输出

Inner:  Welcome to Learn eTutorials

这意味着变量 'get' 记住了内部函数的某些状态,即使外部函数已被删除。

带参数的闭包

为了更好地理解闭包,让我们来看另一个提供参数给 InnerFunction 的示例。到目前为止,我们还没有向 InnerFunction 传递任何参数。

示例:使用闭包查找数字的 n 次方

def n_pow(exp):
    def pow_of(base):
        return pow(base,exp)
    return pow_of

 sec
print(second_pow(3))
print(second_pow(4))
print(second_pow(5))
print("\n")

third_pow = n_pow(3)
print(third_pow(3))
print(third_pow(4))
print(third_pow(5))
print("\n")

fifth_pow =n_pow(5)
print(fifth_pow(2))
print(fifth_pow(3)) 

输出

9
16
25

27
64
125

32
243

在此示例中,我们有一个名为 n_pow 的外部函数,它传递一个名为 exp 的参数。我们还在 n_pow 内部定义了一个名为 pow_of 的函数,它也接受一个参数 basepow_of 函数是外部函数 n_pow 的局部或嵌套函数,它返回我们此处传递的任何参数的幂。需要注意的重要一点是,参数 exp 位于包含作用域中。最后,外部函数返回没有括号的内部函数,这使得该函数成为闭包。

变量 second_pow 在函数调用时存储内部函数的狀態,并将 2 作为参数传递。现在指数的值变为 2。因此,每当我们调用 second_pow 函数来处理任何数字时,它都会产生该基数的平方。这里 second_pow(3) 将 3 作为参数传递给内部函数中的 base,函数返回 3 的平方,即 9。类似地,third_pow 变量在函数调用时将产生指定数字的立方,如示例所示。

使用闭包的好处

  • 闭包是类的完美替代品,特别是当类只有单个方法或少数方法时。
  • 闭包有助于减少全局变量的使用
  • 闭包用作回调函数,从而在一定程度上提供数据隐藏。
  • 闭包用于替换硬编码的常量
  • 闭包被Python 装饰器广泛使用,您将在接下来的教程中学习。
  • 闭包应用于解决组合、偏函数应用、柯里化等数学函数。