Python闭包(Python Closures)介绍

简介: 介绍python的闭包语法。

0. 标题

Python闭包(Python Closures)介绍
这个文章,希望你可以从头到尾读三遍,就可以看懂了,第一遍看不懂很正常。

作者: quantgalaxy@outlook.com   
欢迎交流

1. What: 什么是闭包

1.1 闭包

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。
这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。
闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

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

闭包是为了解决自由变量(free variable)的问题,即在函数内部的变量,可以在函数外被访问和调用。
通过闭包的方式,就可以实现这个功能。

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

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

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

在Python中,闭包(closure)是指一个函数(通常称为内部函数),它包含对在其外部函数中定义的非全局变量的引用。
闭包允许内部函数访问其外部函数的作用域,即使外部函数已经执行完毕。

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闭包和自由变量的原理解释,为什么要有闭包

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

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

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

2.1 python的作用域说明

2.1.1 自由变量

b = 6
def f1(a):
    print(a)
    print(b)

执行后,输出1,6。
函数体外的b为全局变量,函数体内的b为自由变量。
因为自由变量b绑定到了全局变量,所以在函数f1中能正确访问。

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

b = 6
def f2(a):
    print(a)
    print(b)
    b = 2

这次调用时候就报错了:

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声明:

b = 6
def f2(a):
    globle b
    print(a)
    print(b)
    b = 2

2.1.3 闭包的作用域

上个例子中,如果我们不想使用global关键字,还是想把变量当成原来函数的局部变量,我们该如何做呢?
这时候闭包出现了。
闭包是一种函数,它会保留定义时存在的自由变量的绑定,这样调用函数时,
虽然定义作用域不可用了,但是仍然能使用那些绑定。

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定义的局部变量。

作者: quantgalaxy@outlook.com   
欢迎交流

2.1.4 闭包中的nonlocal

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

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的作用是把变量标记为自由变量,即使在函数中为变量赋值了,也仍然是自由变量。

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来说,赋值会隐式创建局部变量。
可变对象添加元素不是赋值,不会隐式创建局部变量。

3. 总结

闭包就是用来解决函数嵌套时,自由变量如何处理的问题,它会保留自由变量的绑定,即使局部作用域已经消失。
对于不可变类型和None来说,赋值会隐式创建局部变量,把自由变量转换为局部变量,
这可能会导致程序报错:局部变量在赋值前进行了引用。
除了使用global声明为全局变量外,还可以使用nonlocal声明把局部变量强制变为自由变量,实现闭包。

4. 作者

作者: quantgalaxy@outlook.com   
欢迎交流
目录
相关文章
|
4天前
|
存储 缓存 算法
Python闭包|你应该知道的常见用例(下)
Python闭包|你应该知道的常见用例(下)
11 1
Python闭包|你应该知道的常见用例(下)
|
7天前
|
自然语言处理 小程序 测试技术
Python闭包|你应该知道的常见用例(上)
Python闭包|你应该知道的常见用例(上)
14 3
Python闭包|你应该知道的常见用例(上)
|
4月前
|
监控 测试技术 Python
颠覆传统!Python闭包与装饰器的高级实战技巧,让你的项目效率翻倍
【7月更文挑战第7天】Python的闭包与装饰器是强大的工具。闭包是能记住外部作用域变量的内部函数,常用于动态函数创建和工厂模式。例如,`make_power`返回含外部变量`n`的`power`闭包。装饰器则允许在不修改函数代码的情况下添加新功能,如日志或性能监控。`my_decorator`函数接收一个函数并返回包装后的函数,添加了前后处理逻辑。掌握这两者,可提升编程效率和灵活性。
40 3
|
29天前
|
Python
深入理解Python中的闭包
深入理解Python中的闭包
16 0
|
4月前
|
存储 安全 Java
在python中使用闭包和其他惯例
【7月更文挑战第3天】本文介绍闭包基本概念和例子,内部函数访问外部变量,实现数据隐藏。以及 Python的惯用法:用`in`检查字典键,用`dict.get()`安全取值。
42 1
在python中使用闭包和其他惯例
|
3月前
|
数据安全/隐私保护 Python
Python闭包:函数定义的神秘力量!
Python闭包:函数定义的神秘力量!
54 0
|
4月前
|
程序员 Python
从零到一,彻底掌握Python闭包与装饰器的精髓,成为编程界的隐藏Boss
【7月更文挑战第7天】探索Python编程的两大基石:闭包与装饰器。闭包是内部函数记住外部作用域的变量,如`make_multiplier_of`返回的`multiplier`,它保持对`n`的引用。装饰器则是函数工厂,接收函数并返回新函数,如`my_decorator`,它在不改变原函数代码的情况下添加日志功能。掌握这些,让代码更优雅,效率更高,助你成为编程高手。
32 3
|
4月前
|
程序员 Python
程序员必看!Python闭包与装饰器的高级应用,让你的代码更优雅、更强大
【7月更文挑战第7天】Python中的闭包和装饰器是高级特性,用于增强代码功能。闭包是内部函数记住外部作用域的变量,常用于动态函数和函数工厂。示例展示了`make_multiplier_of`返回记住n值的`multiplier`闭包。装饰器则是接收函数并返回新函数的函数,用于不修改原函数代码就添加功能。`my_decorator`装饰器通过`@`语法应用到`say_hello`函数上,展示了在调用前后添加额外行为的能力。这两种技术能提升代码的优雅性和效率。
38 3
|
4月前
|
Python
Python编程实战:利用闭包与装饰器优化日志记录功能
【7月更文挑战第7天】Python的闭包和装饰器简化了日志记录。通过定义如`log_decorator`的装饰器,可以在不修改原函数代码的情况下添加日志功能。当@log_decorator用于`add(x, y)`函数时,调用时自动记录日志。进一步,`timestamp_log_decorator`展示了如何创建特定功能的装饰器,如添加时间戳。这些技术减少了代码冗余,提高了代码的可维护性。
61 1
|
4月前
|
Python
Python黑魔法揭秘:闭包与装饰器的高级玩法,让你代码飞起来
【7月更文挑战第7天】Python的闭包和装饰器是提升代码效率的神器。闭包是能记住外部作用域变量的内部函数,常用于动态函数创建。示例中,`make_multiplier_of`返回一个保留`n`值的闭包。装饰器则是一个接收函数并返回新函数的函数,用于在不修改原函数情况下添加功能,如日志或性能追踪。`@my_decorator`装饰的`say_hello`函数在执行时会自动加上额外操作。掌握这两者,能让Python代码更优雅、强大。**
33 1
下一篇
无影云桌面