python 中的闭包

本文介绍了Python中的闭包概念,解释了闭包如何结合函数及其环境变量来提高代码复用性。通过实例展示了闭包的创建及使用方法。

闭包(closure)是函数式编程的重要的语法结构。函数式编程是一种编程范式 (而面向过程编程和面向对象编程也都是编程范式)。在面向过程编程中,我们见到过函数(function);在面向对象编程中,我们见过对象(object)。函数和对象的根本目的是以某种逻辑方式组织代码,并提高代码的可重复使用性(reusability)。闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。

闭包

函数是一个对象,所以可以作为某个函数的返回结果

复制代码
def line_conf():
    def line(x):
        return 2*x+1
    return line       # return a function object

my_line = line_conf()
print(my_line(5))       
复制代码

上面的代码可以成功运行。line_conf的返回结果被赋给line对象。上面的代码将打印11。

 

如果line()的定义中引用了外部的变量,会发生什么呢?

复制代码
def line_conf():
    b = 15
    def line(x):
        return 2*x+b
    return line       # return a function object

b = 5
my_line
= line_conf() print(my_line(5))
复制代码

我们可以看到,line定义的隶属程序块中引用了高层级的变量b,但b信息存在于line的定义之外 (b的定义并不在line的隶属程序块中)。我们称b为line的环境变量。事实上,line作为line_conf的返回值时,line中已经包括b的取值(尽管b并不隶属于line)。

上面的代码将打印25,也就是说,line所参照的b值是函数对象定义时可供参考的b值,而不是使用时的b值。

 

一个函数和它的环境变量合在一起,就构成了一个闭包(closure)。在Python中,所谓的闭包是一个包含有环境变量取值的函数对象。环境变量取值被保存在函数对象的__closure__属性中。比如下面的代码:

复制代码
def line_conf():
    b = 15
    def line(x):
        return 2*x+b
    return line       # return a function object

b = 5
my_line = line_conf()
print(my_line.__closure__)
print(my_line.__closure__[0].cell_contents)
复制代码

__closure__里包含了一个元组(tuple)。这个元组中的每个元素是cell类型的对象。我们看到第一个cell包含的就是整数15,也就是我们创建闭包时的环境变量b的取值。

 

下面看一个闭包的实际例子:

复制代码
def line_conf(a, b):
    def line(x):
        return a*x + b
    return line

line1 = line_conf(1, 1)
line2 = line_conf(4, 5)
print(line1(5), line2(5))
复制代码

这个例子中,函数line与环境变量a,b构成闭包。在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个环境变量的取值,这样,我们就确定了函数的最终形式(y = x + 1和y = 4x + 5)。我们只需要变换参数a,b,就可以获得不同的直线表达函数。由此,我们可以看到,闭包也具有提高代码可复用性的作用。

如果没有闭包,我们需要每次创建直线函数的时候同时说明a,b,x。这样,我们就需要更多的参数传递,也减少了代码的可移植性。利用闭包,我们实际上创建了泛函。line函数定义一种广泛意义的函数。这个函数的一些方面已经确定(必须是直线),但另一些方面(比如a和b参数待定)。随后,我们根据line_conf传递来的参数,通过闭包的形式,将最终函数确定下来。

 

闭包的特点是返回的函数还引用了外层函数的局部变量,所以,要正确使用闭包,就要确保引用的局部变量在函数返回后不能变。举例如下:

# 希望一次返回3个函数,分别计算1x1,2x2,3x3:
def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs

f1, f2, f3 = count()

你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果全部都是 9(请自己动手验证)。

原因就是当count()函数返回了3个函数时,这3个函数所引用的变量 i 的值已经变成了3。由于f1、f2、f3并没有被调用,所以,此时他们并未计算 i*i,当 f1 被调用时:

>>> f1()
9     # 因为f1现在才计算i*i,但现在i的值已经变为3

因此,返回函数不要引用任何循环变量,或者后续会发生变化的变量



Python 中,闭包(Closure)是一种函数与它所引用的自由变量(即不在函数内部定义的变量)之间的绑定关系。闭包能够“记住”并访问其词法作用域,即使函数在其作用域外执行。闭包的核心特征是函数和其引用环境的结合,使其能够访问并操作外部作用域中的变量 [^2]。 ### 闭包的作用 1. **读取函数内部的变量** 闭包可以访问外部函数中的变量,即使外部函数已经执行完毕。这使得内部函数可以保留并操作外部函数的状态。 2. **保持变量在内存中** 由于闭包对外部作用域中的变量保持引用,这些变量不会被垃圾回收机制回收,从而实现状态的持久化。 3. **封装和数据隐藏** 闭包可用于创建私有变量,避免全局变量的污染,实现类似面向对象编程中的私有属性。 4. **函数工厂** 闭包可以用于创建具有不同行为的函数,基于不同的输入参数生成定制化的函数。 ### 闭包的使用示例 以下是一个简单的闭包示例,演示了如何通过闭包保留外部函数的变量状态: ```python def outer_function(x): def inner_function(): print(f"Value of x: {x}") return inner_function closure_example = outer_function(10) closure_example() # 输出: Value of x: 10 ``` 在上述代码中,`inner_function` 是一个闭包,它捕获了 `outer_function` 中的变量 `x`。即使 `outer_function` 已经执行完毕,`closure_example` 仍然可以访问 `x` 的值 [^1]。 另一个常见的应用场景是使用闭包实现计数器: ```python def counter(): count = 0 def increment(): nonlocal count count += 1 return count return increment counter_instance = counter() print(counter_instance()) # 输出: 1 print(counter_instance()) # 输出: 2 ``` 在这个例子中,`increment` 函数是一个闭包,它维护了 `count` 变量的状态,每次调用都会递增该变量。这种方式避免了使用全局变量,同时实现了数据的封装 [^2]。 ### 注意事项 - **内存消耗**:由于闭包会保持对外部变量的引用,可能会导致内存占用增加,特别是在大量使用闭包时需要注意内存管理 [^3]。 - **不可变变量**:在 Python 中,如果闭包引用的是外部函数的局部变量,并且尝试修改其值,则必须使用 `nonlocal` 关键字声明该变量,否则会创建一个新的局部变量 。 - **循环变量问题**:在闭包中引用循环变量时,需要注意变量作用域问题,可能会导致所有闭包共享同一个变量值,而不是各自独立的值。 ### 相关概念 闭包Python 中可以通过 `__closure__` 属性来查看其捕获的变量信息。例如: ```python def outer(x): def inner(): return x return inner closure = outer(5) print(closure.__closure__) # 查看闭包捕获的变量 ``` 通过 `__closure__` 属性,可以检查闭包是否正确捕获了外部变量及其内存地址 [^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值