python 闭包的理解

闭包”是什么,以及,更重要的是,写“闭包”有什么用处。

(个人理解)


1、“闭包”是什么


首先,明确函数的作用域问题:

(1)外层函数f1可以调用内层函数f2,但无法引用f2内部的变量x

(2)内层函数f2可以引用外层函数f1的变量y

def f1(y):
   def f2(x):
        return x+y #内层函数f2可以引用外层函数f1的变量y
    return f2#  return f2() 区别??外层函数f1可以调用内层函数f2
    #return x  但不能引用f2内部的变量x,这样写就错了
    

(3)如果把全局当做根函数(我自己提出的一个概念),那么就有:

全局之于f1,等效于f1之于f2

理解三个层次,从外到内分别是:global-f1-f2

也就是说,在全局可以调用f1,但无法引用f1内部的y;f1可以引用全局变量x

x=5
def f1(y):
   return x+y  #f1可以引用全局变量x
f1(1)#全局可以调用f1
#print(y)但无法引用f1内部的y,这样写就错了

(4)在外层函数里调用内层的变量,how to?

比如,想在global里调用f1内部的变量,怎么办?

思路:只能很曲折地。本来在global只能调用到f1,而不能调用f1内部的变量。

于是只能让调用f1的时候,f1就把内部的变量主动交出来,也就是return 想要访问的变量。

def f1():
      y=1
      return y
foo=f1()

以上就是在全局调用了f1(),f1返回就是y,就这样比较曲折地,全局的foo还是拿到了f1内部的变量y的值。


以上是拿变量,把变量改成函数,也就是在global里想拿到f1内部定义的函数,思路是一样的:

本来在全局是拿不到f1内部的任何东西,但只要f1把内部的东西返回,就可以通过在全局调用f1,来拿到f1内部的东西。

def f1():
  def f2(x): 
       return x
   return f2
g=f1()
g(5)


(5)闭包来了

以上,通过f1主动把f2返回,全局的g拿到了本来拿不到的f1作用域内部的f2函数,这样相当于就可以在全局调用f2了。不过,这还不是闭包。因为这时,你在全局拿到的f2,就是f2本身,跟f1可以说没有关系了。你在执行g=f1()完毕后,f1就被回收了,变量g指向函数f2。后来的g(5)就是在调用f2这个函数,传入参数5,执行。


闭包是更纠结一点的情况:在全局拿到f2,且这个f2还跟f1还有关系。


先明确一下,函数内的变量和函数定义时的形参,作用域完全相同。

def f1(y):
   #y'=5 想说y'和y的作用域完全相同,只不过y'作为变量取值是固定的,y作为形参取值由传入实参决定,是可变的。但这里我们讨论的是作用域而不是取值
   def f2(x):
         #x'=1 x'和x的作用域也是完全相同的
        return x+y
    return f2

因此为了简单说明问题,我们统一写作函数内定义变量。


def f1():
  y=5 
  def f2():
       x=1 
       return x+y
   return f2
g=f1()
g()

这里有三个层次,从外到内分别是:global-f1-f2

其中,

global作用域里有变量g,和函数f1

f1的作用域里有变量y,和函数f2

f2的作用域里有变量x


根据上面的规则,内层可以引用外层的变量,所以f2这一层里虽然没有变量y,但是引用了f1这一层的y,这个是符合规则的。

因为解释器知道f2依赖于变量y,因此,g=f1()执行后,f1和变量y还不会被回收,但是又不是作用域f2里面的东西,成为“自由变量”。


此时,g就是一个闭包,因为它符合闭包是"引用了自由变量的函数的定义"。g这个闭包包含f2的定义,而f2引用了自由变量y


不过这种写法没有价值,因为g()的值永远是6。

一般的用法是g()得是变量,比如实现计数器

def hellocounter ():
   count=[0]
   def counter():
      count[0]+=1
      print('Hello,'',',count[0],' access!')
   return counter
hello=hellocounter()
hello()
hello()
hello()
#输出是1 2 3
#hello是闭包函数,自由变量是count,因为它不是counter这个函数内部的变量,但是却被counter引用。


闭包一定要有自由变量,这个自由变量产生有两个条件:一是内层函数去引用了外层函数的变量,二是内层函数被调用时,外层函数已经不见了,所以变量才会自由。

2、闭包的应用场景

1、保护函数内的变量安全。函数f1中y只有函数f2才能访问,而无法通过其他途径访问到,因此保护了y的安全性。

2、在内存中维持一个变量。依然如前例,由于闭包,函数f1中y的一直存在于内存中,因此每次执行c(),都会给y自加1。

3、闭包会在父函数外部,改变父函数内部变量的值。也就是,在f1的外部,通过g改变f1内部的变量/函数f2的值。

所以,如果把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

也就是如果把f1看成一个对象,把g看做对象的公有方法,f2看做对象的私有属性,那么这时相当于可以做到:通过公有方法改变私有属性,一般是不建议的,违反了类的封装性,使用要小心。

猜你喜欢

转载自blog.csdn.net/m0_37693335/article/details/79568350