当前位置: 首页 > 后端技术 > Python

Python的闭包

时间:2023-03-26 17:00:46 Python

闭包(closure)作为一个不太好理解的概念出现在很多主流编程语言中。Python中的很多高级实现都离不开闭包。装饰器是使用闭包的典型例子。作用域要学习闭包,首先要理解作用域的概念。作用域是程序运行时变量存在的范围。常见的作用域包括全局作用域和局部作用域。定义在全局范围内的变量在程序运行过程中可以在任何地方访问;函数内部定义的变量只能在函数内部访问。内部范围是本地范围。为了便于理解,我在这里称之为函数作用域。全局作用域无法读取函数作用域中的局部变量:deffoo():num=100print(num)#NameError:name'num'isnotdefined函数作用域可以向上读取全局作用域中的全局变量:num=100deffoo():print(num)#100foo()闭包在Python中,函数是一等公民,这意味着您可以像对待int和dict等普通数据类型一样对待它们。事实上,函数不仅可以定义在全局范围内,函数也可以定义在另一个函数范围内。deffoo():num=100defbar():print(num)#100bar()在上面foo()的代码中,foo函数内部又定义了一个bar函数。bar函数可以读取foo函数中定义的任何变量。bar函数读取变量num的顺序是:先在bar函数本身的函数范围内查找,如果没有找到,再向上查找到foo函数的范围内,如果还没有找到,再向上查找到全球范围。上面的代码可以稍微修改一下,变成一个闭包函数:deffoo():num=100defbar():print(num)#100returnbarresult=foo()result()可以看到,只要把前面的改成在bar()函数调用的示例代码,改成返回bar,然后调用foo函数时用一个变量result接收foo函数的返回值,最后调用result变量得到相同的结果。这样就在全局作用域中间接读取了函数作用域中的变量。这就是闭包的力量。像这样,函数嵌套函数,内层函数引用外层函数作用域内的局部变量,外层函数返回内层函数的引用作为返回值。这时候,我们就可以将内部函数和它所引用的外部函数作用域中的局部变量统称为闭包。如果闭包的应用需要实现一个计数器,我们很容易想到用一个类来实现:classCounter(object):def__init__(self):self.num=0def__call__(self):self.num+=1returnself.numcounter=Counter()print(counter())#1print(counter())#2print(counter())#3由于类可以保存数据和操作数据,所以很容易用类来实现柜台。函数本身不能每次调用都保存数据,所以不能实现计数器的功能。但是当我们有一个闭包函数的时候,我们可以用一个函数的形式来实现一个计数器。defmake_counter():num=0defcounter():nonlocalnumnum+=1returnnumreturncountercounter=make_counter()print(counter())#1print(counter())#2print(counter())#morethan3,我们实现一个带有闭包函数的计数器。可以看到counter函数内部有一个语句nonlocalnum,关键字nonlocal的作用可以对应global关键字来理解。当我们在函数作用域内部修改全局作用域的不可变类型变量时,我们会使用global关键字来表示一个变量是一个全局变量,同样的nonlocal关键字用来表示num是一个闭包中的变量,其中有一个专业术语叫自由变量。一般来说,一个函数执行完后,它的内部变量会被销毁,但是自由变量num不会立即被销毁,它和计数器函数一起形成了一个闭包。闭包的陷阱以下是使用闭包时常见的误解:deffunc():li=[]foriinrange(3):deff():returnili.append(f)returnlili=func()forfinli:print(f())#运行结果#2#2#2我们期望打印结果依次为0,1,2,但实际运行上面的代码,结果确实是2,2,2、这是因为内部函数f和循环变量i形成了一个闭包。func函数内部一共生成了3个f函数,依次追加到li列表中,但并没有立即执行,而是在内部引用了自由变量i,经过3次for循环后i的值发生了变化执行完毕。变成了2,当li在函数外接收到,再次循环执行每一个f函数,打印出来的i的值自然就是2了。为了解决这个问题,我们需要想办法让内部闭包函数f在每次循环中绑定当前循环变量,使其不随着i的变化而变化:deffunc():li=[]foriinrange(3):defwrap(j):deff():returnjreturnfli.append(wrap(i))returnlili=func()forfinli:print(f())#run结果#0#1#2这里,我在内部函数f外面嵌套了一层函数wrap,在执行li.append(wrap(i))语句时,将循环变量i的值传递给wrap函数,这样wrap函数执行完成后,函数f和变量j就形成了一个闭包。每次执行for循环时,循环变量i作为参数传递给wrap函数。由于每个生成的wrap函数都是相互独立的,函数参数绑定的变量的值不会改变,所以可以保证在每次循环过程中wrap函数内部保存的变量j是相互独立的,所以预期最终可以得到结果。起始地址:https://jianghushinian.cn/