目录
-------------------------------------------------------------------------------------------------------------------------
一、Python中对闭包相关定义
(一)、闭包的定义
在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,就把这个使用了外部函数变量的内部函数称为闭包
(二)、闭包构成条件
1、存在函数嵌套
2、内部函数使用了外部函数的变量(包括外部函数的参数)
3、外部函数返回了内部函数
(三)、闭包的作用
1、数据封装:闭包可以用于封装私有数据,只暴露有限的接口供外界访问。
2、保持变量状态:闭包允许函数记住和访问其词法作用域中的变量,即使函数在其作用域之外执行。
3、延迟计算:通过闭包可以推迟计算的执行,直到真正需要结果时。
二、python中闭包的设计底层机制保障
(一)、nonlocal关键字
通过nonlocal关键字,内层函数可以访问外层函数变量。
为了让内存函数能引用外层函数中的变量,python在底层机制上设计加了nonlocal关键字,如果没有这个关键字,内层函数就无法引用外部变量,闭包大部分设计初衷(数据封装等)就无法完成。
(二)、函数如存在被引用,则函数不释放
B函数能访问A函数:但B函数被包裹在A里面,在闭包这种设计机制下,如果最终的B函数还是不能被外界调用,没有意义。于是这时候我把B函数引出来,通过A函数返回。那么这时就B函数就相当于暴露给外界了,随便调用了,也就变成正常函数了。
(三)、嵌套函数机制
函数内部定义函数的机制
比如C语言就没有原生支持嵌套函数。python闭包通过嵌套函数,巧妙的把内部接口引出。
Python底层通过支持上述机制,从而使得闭包的设计形式称为可能。
三、闭包的设计典型代码
通过上述代码设计,外层counter函数通过接口可以访问内部变量,由于Python设计成返回后函数不销毁的机制,那么里面的变量可以长期存在,而外部函数又能通过接口访问它,也让这个变量有事实上全局变量的功能。
四、闭包功能设计分析
(一)、数据封装:
上面代码就是闭包提供的典型数据封装例子,函数内局部变量可以替代全局变量。具体过程如下图:
(二)、存在类设计做数据封装,为什么需要闭包功能?
那如果闭包只是为了提供这种形式的局部变量,在Python中,类的变量封装和闭包都是实现数据封装的手段,但它们的应用场景、实现机制以及提供的功能有所不同。
(三)、类的变量封装
1、应用场景:
类主要用于定义对象的蓝图,它封装了数据(属性)和操作这些数据的方法(行为)。当你需要创建多个具有相同结构但不同数据的对象时,类是一个自然的选择。
2、实现机制:
类通过将变量标记为私有(通常以两个下划线__开头)来限制外部直接访问,从而实现封装。私有变量只能通过类的公有方法(getter和setter)来访问和修改,这样可以控制对数据的访问,并在必要时进行验证或转换。
3、功能特点:
提供了面向对象编程的特性,如继承、多态等。
可以通过实例化创建多个独立的对象,每个对象有自己的状态。
更易于维护和扩展,特别是在大型项目中,类的封装有助于模块化设计。
(四)、闭包的数据封装
1、应用场景:
闭包常用于函数式编程,当需要创建具有特定状态的函数时,闭包可以捕获并记住其周围作用域的变量。适用于创建一次性使用的工具函数、装饰器或是管理状态的小型逻辑单元。
2、实现机制:
闭包是由一个内部函数引用其外部函数作用域中的变量而形成的。即使外部函数执行完毕,内部函数仍然可以访问这些外部变量。这种机制自然地“封装”了这些变量,使得它们不被外部直接访问。
3、功能特点:
适用于不需要或不希望创建对象的情况,减少内存占用。
提供了一种轻量级的状态管理方式,特别适合处理短暂存在的状态或计算过程。
便于编写简洁、可读性强的代码,尤其是在处理函数式编程任务时。
4、两者对比
类的封装更适合需要复杂对象结构、面向对象特性和长期状态管理的场景。
闭包的数据封装则更适合处理函数间的状态传递、短期状态保持或简单的数据隐藏需求,特别是在不需要完整类结构的情况下。
两者各有优势,选择哪种方式取决于具体的需求、项目的规模以及设计哲学。
5、保持变量状态
和闭包的数据封装基本相同。
6、延迟计算
延迟计算实际上典型应用在装饰器上,现在以装饰器为例说明。
有时候闭包并没有外部函数引用局部变量的需求,闭包定义中要求内部函数使用了外部函数的变量,其实这时候并没有引用外层函数中的外部变量,只是需要增加的功能直接放在外层函数中。 在闭包典型引用中的装饰器就是这样的。第一次只是传入原被装饰函数,返回内层函数地址后,将返回这个地址直接复制给原被装饰函数命名的地址变量。然后利用这个地址变量,带入被装饰函数的参数直接再次调用内层函数。
编辑
7、为什么要设计闭包的延迟计算
import time
def log_decorator(func):
start_time = time.time()
func()
end_time = time.time()
execution_time = end_time - start_time
print(f"Function {func.__name__} executed in {execution_time} seconds")
def say_hello():
time.sleep(1)
print("Hello, world!")
log_decorator(say_hello)
上述代码也没有设计成闭包形式,但是同样达到效果了。为什么不采用这种方式?
我的理解有两点:
一是改变了调用方式:原来调用形式为:say_hello(),现在为log_decorator(say_hello)
二是上面形式如果装饰器本身有参数的话,,只能写成
def log_decorator(parm)(func)形式,内层外层函数没有分开,层次不分明,调用形式比较繁琐,不够简洁。
五、闭包技术必要性和产生原因及优势
(一)、产生原因
闭包技术产生需求本质是横切面编程的需要,横切面编程中需要一个函数访问(获得)另外一个函数内部值,或者需要给另外一个函数增加额外功能,于是横切面编程结合面向对象编程,所以就催生了闭包技术。
(二)、闭包设计优势
1、结构性紧凑
两个函数绑定在一起,你想访问我内部函数吗?那你必须被我包裹起来。代码逻辑上一看就知道谁引用了我。
2、逻辑顺序严谨
把B定义包含在A里面,第一次调用A,只是执行了A,这就保证了逻辑的先后顺序的100%可靠。第二次才执行B本身,然后再由A返回给第三方调用。
3、调用方式不变
访问了A函数内部变量的B函数,当然其他函数C要能引用B才有意义,正好B包机制能返回B函数地址供调用。保证了调用形式不变,设计非常巧妙。
(三)、闭包带来问题
正常设计机制应该外层函数执行完返回值后应该销毁,但是为了符合闭包设计初衷,外层函数有指向地址的函数返回后不释放,PYTHON加上这个机制。但是大量这种应用将会造成内存不释放,最终造成系统崩溃。