当前位置: 首页 > news >正文

python编程-闭包

目录

一、闭包的概念

更深入理解闭包记住的环境信息

二、闭包的形成条件

1. 嵌套函数

关于嵌套函数的多种形式

2. 内部函数引用外部函数的变量

3. 外部函数返回内部函数

三、闭包的工作原理

内存中的具体存储结构

四、闭包的用途

1. 数据隐藏和封装

数据隐藏和封装的更多应用场景

2. 函数工厂

函数工厂的扩展应用

3. 延迟执行和回调函数

延迟执行和回调函数的深入应用

五、闭包的注意事项

1. 变量的生命周期和内存管理

方法一:将闭包函数返回值设置为 None

方法二:使用 del 关键字删除闭包函数及相关引用

方法三:重构代码,让闭包的生命周期更合理

变量的生命周期和内存管理的进一步讨论

2. 变量的可变性和作用域

变量的可变性和作用域的补充说明


一、闭包的概念

闭包是一种特殊的函数对象,它在函数定义的基础上,记住了定义它时所处的环境(即函数外部的变量及其取值等信息),即使在定义它的函数已经执行完毕之后,闭包函数仍然能够访问和操作这些外部环境中的变量。简单来说,闭包是由函数及其相关的引用环境组合而成的实体。

 

更深入理解闭包记住的环境信息

闭包所记住的定义它时所处的环境,不仅仅局限于简单的变量值,还包括变量的类型、所在的模块、甚至是函数调用时的上下文信息等。

例如,如果外部函数中定义的变量是一个来自特定模块的对象,闭包在后续执行时依然能够依据记住的模块信息正确地访问和操作该对象的属性与方法。

假设我们有以下代码:

import datetimedef outer_function():now = datetime.datetime.now()def inner_function():print(f"创建闭包时的时间是:{now}")return inner_functionclosure = outer_function()
closure()

在这个例子中,闭包记住了 now 这个 datetime.datetime 类型的对象,以及它来自 datetime 模块的信息,所以在调用闭包函数时能够正确地打印出创建闭包时的时间。

 

二、闭包的形成条件

要形成一个闭包,需要满足以下几个关键条件:

1. 嵌套函数

必须存在函数的嵌套定义,即一个函数内部定义了另一个函数。例如:

def outer_function():# 这里是外部函数的函数体def inner_function():# 这里是内部函数的函数体passreturn inner_function

在上述代码中,outer_function是外部函数,inner_function是在outer_function内部定义的内部函数,这种嵌套结构是形成闭包的基础。

 

关于嵌套函数的多种形式

除了常见的一层嵌套(即一个函数内部定义另一个函数),还可以存在多层嵌套的情况,形成更复杂的闭包结构。例如:

def outer_most_function():x = 10def middle_function():y = 20def inner_function():print(x + y)return inner_functionreturn middle_function()closure = outer_most_function()
closure()  # 输出:30

在上述代码中,outer_most_function 内部嵌套了 middle_function,而 middle_function 内部又嵌套了 inner_function,这种多层嵌套同样满足闭包的形成条件,并且内部的 inner_function 能够访问到外层函数定义的变量 x 和中层函数定义的变量 y

2. 内部函数引用外部函数的变量

内部函数需要引用外部函数中的变量。继续以上面的代码为例,假如我们在inner_function中引用了outer_function中的变量,如下所示:

def outer_function():x = 10def inner_function():print(x)return inner_function

在这里,inner_function引用了outer_function中定义的变量x,这是闭包能够 “记住” 外部环境的关键因素。

3. 外部函数返回内部函数

外部函数需要返回内部函数对象,使得在外部函数执行完毕后,内部函数仍然可以在其他地方被调用,并且保留对外部函数变量的访问能力。例如:

def outer_function():x = 10def inner_function():print(x)return inner_functionclosure = outer_function()
closure()

在这个例子中,outer_function返回了inner_function,然后我们将返回的函数对象赋值给closure变量,通过closure()就可以调用inner_function,此时即使outer_function的执行已经完成,inner_function依然能够访问x的值(这里会输出10)。

三、闭包的工作原理

当创建闭包时,Python 会在内存中创建一个特殊的结构来保存闭包的相关信息。具体来说:

  • 函数对象的创建:内部函数inner_function本身是一个函数对象,它包含了函数的代码逻辑、参数列表等常规函数属性。

  • 引用环境的保存:除了函数本身的属性,Python 还会保存内部函数所引用的外部函数变量的引用。在上面的例子中,当outer_function返回inner_function时,Python 会记住inner_function引用了outer_function中的变量x,并且会将这个引用关系与inner_function的函数对象一起保存起来。

  • 变量的访问机制:当后续调用闭包函数(如closure())时,Python 会根据保存的引用关系,在闭包的引用环境中查找被引用的变量。所以,即使outer_function已经执行结束,其内部定义的变量所在的内存空间可能已经在常规情况下被释放,但由于闭包保存了对这些变量的引用,这些变量仍然能够被闭包函数访问到,就好像它们仍然存在于一个特殊的 “闭包环境” 中一样。

内存中的具体存储结构

当创建闭包时,Python 在内存中创建的特殊结构类似于一个包含函数对象和其引用环境的 “包裹”。这个 “包裹” 会记录函数的代码对象(包含函数的定义、参数等信息)以及对外部变量的引用关系。

以之前的 outer_function 和 inner_function 为例,内存中的这个特殊结构会保存 inner_function 的代码对象以及它对 outer_function 中定义的变量 x 的引用信息。当调用闭包函数时,Python 会依据这个存储结构,先找到函数的代码对象来执行函数体,同时根据引用关系获取对应的外部变量值进行操作。

四、闭包的用途

闭包在 Python 编程中有多种用途,以下是一些常见的应用场景:

1. 数据隐藏和封装

闭包可以用于隐藏数据,使得外部无法直接访问某些变量,只有通过闭包函数提供的特定接口才能操作这些数据。例如:

def counter():count = 0def increment():nonlocal countcount += 1return countreturn incrementmy_counter = counter()
print(my_counter())  
print(my_counter())  

在这个例子中,count变量被封装在counter函数内部,外部无法直接访问它。只有通过increment函数(闭包)才能对count进行递增操作并获取其值,实现了一定程度的数据隐藏和封装

 

数据隐藏和封装的更多应用场景

  • 配置管理:在一些应用程序中,需要根据不同的配置参数来执行不同的操作,但又不想让这些配置参数被随意修改。闭包可以很好地实现这一点。例如:
def config_manager(config):def execute_task():if config["mode"] == "debug":print(f"以调试模式执行任务,参数:{config}")elif config["mode"] == "production":print(f"以生产模式执行任务,参数:{config}")return execute_taskdebug_config = {"mode": "debug", "param1": "value1"}
production_config = {"mode": "production", "param1": "value1"}debug_task = config_manager(debug_config)
production_task = config_manager(production_config)debug_task()
production_task()

在这个例子中,config 参数被封装在闭包内部,只有通过对应的闭包函数debug_task 和 production_task)才能根据不同的配置执行相应的任务,实现了配置参数的隐藏和封装。

 

  • 权限管理:闭包也可用于权限管理,限制对某些资源的访问。例如:
def access_manager(has_permission):def access_resource():if has_permission:print("有权限访问资源")else:print("无权限访问资源")return access_resourceuser1_access = access_manager(True)
user2_access = access_manager(False)user1_access()
user2_access()

这里,通过闭包函数 access_resource 根据传入的权限标志 has_permission 来决定是否有权限访问资源,实现了对资源访问权限的管理,并且 has_permission 这个变量被隐藏在闭包内部,外部无法直接修改它。

2. 函数工厂

闭包可以作为函数工厂,根据不同的输入参数生成具有不同行为的函数。例如:

def power_factory(exponent):def power(base):return base ** exponentreturn powersquare = power_factory(2)
cube = power_factory(3)print(square(5))  
print(cube(5))  

在这里,power_factory函数根据传入的exponent参数生成了不同的power函数(闭包)。square函数用于计算一个数的平方,cube函数用于计算一个数的立方,通过闭包实现了函数的动态生成

 

函数工厂的扩展应用

  • 动态生成验证函数:在表单验证等场景中,可以利用闭包生成不同的验证函数来检查输入数据是否符合特定的条件。例如:
def validation_factory(field_type):def validate(value):if field_type == "email":if "@" in value and "." in value:return Truereturn Falseelif field_type == "password":if len(value) >= 8:return Truereturn Falsereturn validateemail_validate = validation_factory("email")
password_validate = validation_factory("password")print(email_validate("test@example.com"))
print(password_validate("12345678"))

在这个例子中,validation_factory 根据传入的 field_type 参数生成不同的验证函数(闭包),用于验证不同类型的输入数据是否符合要求。

  • 生成特定格式的输出函数:闭包还可以用于生成按照特定格式输出数据的函数。例如:
def format_factory(format_str):def format_output(data):return format_str.format(data)return format_outputcurrency_format = format_factory("${0}")
percentage_format = format_factory("{0}%")print(currency_format(100))
print(percentage_format(0.5))

这里,format_factory 生成的闭包函数 format_output 能够根据传入的不同格式字符串将输入数据按照特定格式输出。

3. 延迟执行和回调函数

闭包可以用于实现延迟执行某些操作或者作为回调函数在特定事件发生时被调用。例如:

def delayed_execution(message):def execute():print(message)return executeaction = delayed_execution("这是延迟执行的消息")
action()

在这个例子中,delayed_execution函数返回的闭包execute可以在后续的某个时刻被调用,实现了消息的延迟打印,类似于设置了一个延迟执行的任务。在一些异步编程、事件驱动编程等场景中,闭包常被用作回调函数,在特定事件完成或满足一定条件时被触发执行。

 

延迟执行和回调函数的深入应用

  • 异步任务调度:在异步编程中,闭包常被用作回调函数来处理异步任务完成后的结果。例如:
import asyncioasync def async_task():await asyncio.sleep(2)return "异步任务完成"def callback_factory(result_callback):async def task_callback(task):result = await taskresult_callback(result)return task_callbackdef print_result(result):print(f"异步任务结果:{result}")callback = callback_factory(print_result)asyncio.run(async_task())
asyncio.run(callback(async_task()))

在这个例子中,callback_factory 生成的闭包函数 task_callback 作为回调函数,在异步任务 async_task 完成后,会调用传入的 result_callback 函数(这里是 print_result)来处理任务结果,实现了异步任务结果的处理和延迟执行(因为要等待异步任务完成后才执行回调函数)。

  • 事件驱动编程中的应用:在事件驱动编程中,闭拉常被用作回调函数,在事件发生时执行特定的操作。例如,在一个简单的图形界面应用程序中,当用户点击某个按钮时,会触发一个事件,我们可以使用闭包来定义在这个事件发生时要执行的操作。

假设我们有一个简单的按钮点击事件处理框架:

def button_click_handler_factory(action):def button_click_handler():action()return button_click_handlerdef print_message():print("按钮被点击了")handler = button_click_handler_factory(print_message)# 这里假设在图形界面应用程序中,当按钮被点击时,会调用handler函数
handler()

在这个例子中,button_click_handler_factory 生成的闭包函数 button_click_handler 作为按钮点击事件的处理函数,当按钮被点击时(假设在实际的图形界面应用程序中会触发调用这个函数),会执行传入的 action 函数(这里是 print_message),实现了在事件发生时的特定操作。

五、闭包的注意事项

在使用闭包时,也需要注意一些问题:

1. 变量的生命周期和内存管理

虽然闭包能够记住外部函数的变量,但如果不小心处理,可能会导致内存泄漏等问题。特别是当闭包函数长期存在并且不断引用一些大型数据结构或对象时,这些被引用的对象可能无法被正常垃圾回收,因为闭包一直保持着对它们的引用。例如:

def memory_leak_example():large_data = [i for i in range(1000000)]def closure_function():print(large_data)return closure_functionleak_closure = memory_leak_example()
# 这里如果不及时释放闭包引用,large_data可能一直占用内存空间,导致内存泄漏

为了避免这种情况,可以在适当的时候手动释放闭包对变量的引用,或者确保闭包的生命周期是合理的,不会导致不必要的内存占用。

 

方法一:将闭包函数返回值设置为 None

在使用完闭包函数后,将保存闭包函数的变量设置为 None,这样可以让 Python 的垃圾回收机制知道该闭包对象不再被使用,从而有可能回收其占用的内存,包括闭包内部引用的变量所占用的内存。

示例代码如下:

def memory_leak_example():large_data = [i for i in range(1000000)]def closure_function():print(large_data)return closure_functionleak_closure = memory_leak_example()
leak_closure()  # 调用闭包函数,此时会打印出 large_data# 将闭包函数引用设置为 None,以便垃圾回收机制回收内存
leak_closure = None

在上述代码中,通过 leak_closure = None 这一行,明确告知 Python 解释器 leak_closure 这个闭包对象不再被需要,从而给垃圾回收机制一个信号,让它在合适的时候回收闭包及其引用的变量所占用的内存空间。

 

方法二:使用 del 关键字删除闭包函数及相关引用

del 关键字可以用于删除对象的引用,当一个对象没有任何引用指向它时,Python 的垃圾回收机制会在适当的时候回收该对象占用的内存。

示例代码如下:

def memory_leak_example():large_data = [i for i in range(1000000)]def closure_function():print(large_data)return closure_functionleak_closure = memory_leak_example()
leak_closure()  # 调用闭包函数,此时会打印出 large_data# 使用 del 关键字删除闭包函数引用
del leak_closure# 也可以删除闭包函数内部引用的变量,如果在合适的上下文中
# del large_data (这里在示例函数中直接删除 large_data 可能会导致后续调用闭包函数出错,所以要根据具体情况谨慎使用)

在上述代码中,通过 del leak_closure 这一行,删除了对闭包函数的引用,使得闭包函数对象有可能被垃圾回收机制回收。需要注意的是,对于 large_data 变量,直接在示例函数外部删除它可能会导致后续如果再次调用闭包函数(虽然在这个示例中没有再次调用的情况,但在更复杂的场景下可能会有)时出错,因为闭包函数内部仍然引用着这个变量。所以是否删除 large_data 要根据具体的代码上下文和需求来决定。

方法三:重构代码,让闭包的生命周期更合理

有时候,通过重构代码的方式可以让闭包的生命周期更加合理,避免出现不必要的内存占用。

例如,在上述示例中,可以将 large_data 的生成和闭包函数的定义放在一个更小的作用域内,使得 large_data 在不需要的时候能够及时被销毁。

示例代码如下:

def memory_leak_example():def inner_function():large_data = [i for i in range(1000000)]def closure_function():print(large_data)return closure_functionreturn inner_function()leak_closure = memory_leak_example()
leak_closure()  # 调用闭包函数,此时会打印出 large_data# 这里因为 large_data 是在 inner_function 的作用域内定义的,当 inner_function 执行完毕后,
# large_data 的引用计数会减少,在合适的时候会被垃圾回收机制回收

在上述重构后的代码中,large_data 是在 inner_function 的作用域内定义的,当 inner_function 执行完毕后,large_data 的引用计数会减少,并且在合适的时候会被垃圾回收机制回收,而不需要像之前那样手动去释放闭包对 large_data 的引用,因为其生命周期已经更加合理,不会导致不必要的内存占用了。

 

变量的生命周期和内存管理的进一步讨论

  • 循环引用导致的内存问题:除了之前提到的闭包长期引用大型数据结构可能导致内存泄漏外,还可能出现循环引用的情况,进一步加重内存问题。例如:
def circular_reference_example():class MyClass:def __init__(self):self.closure = Nonemy_obj = MyClass()def outer_function():x = 10def inner_function():print(x)my_obj.closure = inner_functionreturn inner_functionclosure = outer_function()closure()# 这里形成了一个循环引用,my_obj引用了closure,而closure又引用了outer_function中的x,# 可能导致内存无法正常回收,即使不再使用closure,内存也不会释放

在这个例子中,my_obj 的 closure 属性指向了闭包函数 inner_function,而 inner_function 又引用了 outer_function 中的 x,形成了一个循环引用。这种情况下,即使后续不再使用 closure,由于循环引用的存在,内存可能无法正常回收。解决这种循环引用导致的内存问题,通常需要更加细致地管理对象的引用关系,比如在合适的时候将 my_obj.closure 设置为 None,或者使用一些垃圾回收辅助工具来检测和处理循环引用。

  • 不同版本 Python 对内存管理的影响:不同版本的 Python 在处理闭包和内存管理方面可能存在一些差异。例如,Python 2 和 Python 3 在某些情况下对闭包中变量的处理方式可能不同,这可能会影响到内存的使用和回收情况。在实际开发中,需要根据所使用的 Python 版本来关注这些差异,确保代码在不同版本上都能正常运行且不会出现内存相关的问题。

2. 变量的可变性和作用域

如果在闭包中引用的外部函数变量是可变的(如列表、字典等),在闭包函数内部对其进行操作时,需要注意可能会对外部函数的逻辑产生影响。例如:

def outer_function():my_list = [1, 2, 3]def inner_function():my_list.append(4)print(my_list)return inner_functionclosure = outer_function()
closure()
# 这里会修改外部函数中的my_list变量,可能会影响到依赖该变量原始状态的其他逻辑

在这种情况下,需要根据具体的需求来谨慎处理可变变量在闭包中的操作,以确保整个程序的逻辑正确性。同时,在闭包函数中如果需要修改外部函数中的非全局变量,可能需要使用nonlocal关键字来声明,以便明确告知 Python 要修改的是外部函数中的那个变量(如前面counter例子中的count变量)。

变量的可变性和作用域的补充说明

  • 全局变量在闭包中的影响:如果在闭包中引用了全局变量,并且在闭包函数内部对其进行了操作,同样需要注意可能会对整个程序的逻辑产生影响。例如:
count = 0def outer_function():def inner_function():global countcount += 1print(count)return inner_functionclosure = outer_function()
closure()
closure()

在这个例子中,闭包函数 inner_function 引用了全局变量 count,并且在内部对其进行了递增操作。这种情况下,由于全局变量是被整个程序共享的,所以在闭包中对其进行的任何操作都会影响到整个程序中依赖该变量的其他逻辑。因此,在使用闭包时,如果涉及到全局变量,需要特别谨慎地考虑其对整个程序的影响。

  • 使用 nonlocal 关键字的更多细节:当使用 nonlocal 关键字来声明要修改的外部函数中的非全局变量时,需要注意其适用范围。nonlocal 关键字只能用于声明已经存在于外层函数中的变量,并且不能用于声明全新的变量。例如:
def outer_function():x = 10def inner_function():nonlocal xx += 1print(x)return inner_functionclosure = outer_function()
closure()
closure()

在这个例子中,nonlocal x 声明了要修改的是外层函数 outer_function 中的变量 x,并且可以正常地对其进行修改。但是,如果在 inner_function 中尝试使用 nonlocal 关键字来声明一个全新的变量,比如 nonlocal y(假设 y 之前未在任何外层函数中定义),将会抛出异常,因为 nonlocal 关键字不支持这种用法。


http://www.mrgr.cn/news/59533.html

相关文章:

  • 要用PHP开发一个网页,允许用户暂定以1元人民币兑换EACO,并根据EACO价格波动兑换中东国家的货币
  • LabVIEW显微镜自动对焦系统
  • 第二章 I O 输入输出简介 - Open 命令
  • css设置滚动条样式
  • 4款免费音频剪辑软件带你开启声音创作之旅
  • 【Python爬虫实战】Selenium自动化网页操作入门指南
  • 【unity】【游戏开发】Unity代码不给提示怎么办?
  • 租房业务全流程管理:Spring Boot系统应用
  • MTALAB guide 串口简洁版1
  • c++——哈希表的模拟实现
  • 关于Java中**optional,stream,lambda**
  • 特殊主题短视频资源推荐
  • 好用的骨传导运动耳机有哪些?2024年五款高口碑骨传导耳机分享
  • 100种算法【Python版】第14篇——Pollard‘s Rho 质因数分解算法
  • 10.Linux按键驱动-中断的线程化处理threadirq
  • 【LeetCode热题100】链表
  • 虚拟化平台
  • 《深入浅出HTTPS​​》读书笔记(2):HTTP
  • 【日常知识点】Java 语法糖,你用过几个?
  • 【日常知识点】到底推不推荐用JWT?
  • 007:点云处理软件TrimbleRealWorks12.0安装教程
  • 影刀RPA实战:验证码识别功能指令
  • 【系统架构设计师】案例分析预测试卷一(3道材料题)
  • 实时时钟芯片DS1302在STM32系列使用详解
  • 2025考研各省市网上确认时间汇总!
  • Leetcode11:盛水最多的容器