Python闭包(Python Closures)介绍

2023-09-24 11:39:51 浏览数 (1)

0. 标题

Python闭包(Python Closures)介绍

这个文章,希望你可以从头到尾读三遍,就可以看懂了,第一遍看不懂很正常。

代码语言:txt复制
作者: quantgalaxy@outlook.com   
欢迎交流   

1. What: 什么是闭包

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。

这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。

闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

另一种解释:闭包通常用于创建函数工厂,即返回函数的函数。当外部函数返回内部函数时,内部函数会保留对外部函数作用域的引用,形成闭包。

闭包是为了解决自由变量(free variable)的问题,即在函数内部的变量,可以在函数外被访问和调用。

通过闭包的方式,就可以实现这个功能。

Python不要求声明变量,而是假定在函数定义体中赋值的变量是局部变量。

闭包是一种函数,它会保留定义时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍然能使用那些绑定。

上面的说明,可能有些拗口,我们通过后面例子来具体说明下,看完例子后,再来看这个说明,就很好理解了。

2. How: python中的闭包如何使用

在Python中,闭包(closure)是指一个函数(通常称为内部函数),它包含对在其外部函数中定义的非全局变量的引用。

闭包允许内部函数访问其外部函数的作用域,即使外部函数已经执行完毕。

代码语言:python代码运行次数:0复制
def outer_function(x):
    # 在外部函数中定义一个变量
    outer_variable = x

    # 在外部函数中定义一个内部函数
    def inner_function(y):
        # 内部函数可以访问外部函数的变量
        return outer_variable   y

    return inner_function

# 创建一个闭包函数
closure = outer_function(10)  # x is 10。返回了inner_function,并且inner_function内部的outer_variable为10

# 使用闭包函数
result = closure(5)  # y is 5
print(result)  # 输出 15

在这个示例中,outer_function 是外部函数,它接受一个参数 x 并定义了一个内部函数 inner_function,

内部函数引用了 outer_variable,这个变量是外部函数的参数。

当我们调用 outer_function(10) 时,它返回了 inner_function,并且 outer_variable 的值10被保持在内存中。

然后,我们可以多次调用 closure(5),每次它都会使用之前存储的 outer_variable(10) 的值,所以结果是 10 5 = 15。

闭包在许多情况下非常有用,例如在函数工厂、装饰器和回调函数等编程模式中。

它们允许您封装状态和行为,以便稍后在程序的不同部分使用。

3. Why:python闭包和自由变量的原理解释,为什么要有闭包

开始对闭包介绍的时候,有这么一段话:

代码语言:txt复制
Python不要求声明变量,而是假定在函数定义体中赋值的变量是局部变量。  
闭包是一种函数,它会保留定义时存在的自由变量的绑定,这样调用函数时,  
虽然定义作用域不可用了,但是仍然能使用那些绑定。  

我们这里就对python的作用域具体解释下,以及说明下闭包出现解决了什么问题。

3.1 python的作用域说明

3.1.1 自由变量

代码语言:python代码运行次数:0复制
b = 6
def f1(a):
    print(a)
    print(b)

执行后,输出1,6。

函数体外的b为全局变量,函数体内的b为自由变量。

因为自由变量b绑定到了全局变量,所以在函数f1中能正确访问。

3.1.2 全局变量和局部变量互斥

代码语言:python代码运行次数:0复制
b = 6
def f2(a):
    print(a)
    print(b)
    b = 2

这次调用时候就报错了:

代码语言:python代码运行次数:0复制
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 3, in f2
UnboundLocalError: local variable 'b' referenced before assignment

报错:局部变量b在赋值前进行了引用。

这不是缺陷,而是Python设计:Python不要求声明变量,而是假定在函数定义体中赋值的变量是局部变量。

如果想让解释器把b当做全局变量,那么需要使用global声明:

代码语言:python代码运行次数:0复制
b = 6
def f2(a):
    globle b
    print(a)
    print(b)
    b = 2

3.1.3 闭包的作用域

上个例子中,如果我们不想使用global关键字,还是想把变量当成原来函数的局部变量,我们该如何做呢?

这时候闭包出现了。

闭包是一种函数,它会保留定义时存在的自由变量的绑定,这样调用函数时,

虽然定义作用域不可用了,但是仍然能使用那些绑定。

代码语言:python代码运行次数:0复制
def make_averager():
    series = []
    
    def averager(new_value):
        # series是自由变量
        series.append(new_value)
        total = sum(series)
        return totle / len(series)
    
    return averager

avg = make_averager()
avg(10)  # 10.0
avg(11)  # 10.5
avg(12)  # 11.0

这个例子没有报错,就是因为series被内部函数averager引用后,形成了闭包,

闭包会保留自由变量series的绑定,在调用avg(10)时继续使用这个绑定,即使make_averager()函数的局部作用域已经消失。

这就是闭包的作用,通过返回一个内部函数的方式,保留了对自由变量的绑定,解决了自由变量访问的问题,

并没有使用global关键字,但是可以访问到了外部函数make_averager定义的局部变量。

代码语言:txt复制
作者: quantgalaxy@outlook.com   
欢迎交流   

3.1.4 闭包中的nonlocal

是不是所有自由变量都可以通过闭包直接访问呢?其实还有有一些区别的,看下面这个例子:

代码语言:python代码运行次数:0复制
def make_averager():
    count = 0
    total = 0
    
    def averager(new_value):
        count  = 1
        total  = new_value
        return total / count
        
    return averager

运行后会报错:局部变量count在赋值前进行了引用。因为count =1等同于count = count 1,存在赋值,count就变成局部变量了。

total也是如此。

这里如果把count和total通过global关键字声明为全局变量,显然是不合适的,它们作用域最多只扩展到make_averager()函数内。

为了解决这个问题,Python3引入了nonlocal关键字声明,

nonlocal的作用是把变量标记为自由变量,即使在函数中为变量赋值了,也仍然是自由变量。

代码语言:python代码运行次数:0复制
def make_averager():
    count = 0
    total = 0
    
    def averager(new_value):
        nonlocal count, total
        count  = 1
        total  = new_value
        return total / count
        
    return av

注意,对于列表、字典等可变类型来说,添加元素不是赋值,不会隐式创建局部变量。

对于数字、字符串、元组等不可变类型以及None来说,赋值会隐式创建局部变量。

可变对象添加元素不是赋值,不会隐式创建局部变量。

4. 总结

闭包就是用来解决函数嵌套时,自由变量如何处理的问题,它会保留自由变量的绑定,即使局部作用域已经消失。

对于不可变类型和None来说,赋值会隐式创建局部变量,把自由变量转换为局部变量,

这可能会导致程序报错:局部变量在赋值前进行了引用。

除了使用global声明为全局变量外,还可以使用nonlocal声明把局部变量强制变为自由变量,实现闭包。

5. 作者信息

代码语言:txt复制
作者: quantgalaxy@outlook.com   
欢迎交流   

0 人点赞