函数之闭包的解释

可参考自己廖雪峰python从零开始(五)

python之闭包:讲的很好
函数式编程之闭包
关于函数闭包的代码解释
解释1:如果在一个内部函数里,对在外部作用域(但不在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。定义在外部函数内的但由内部函数引用或者使用的变量被称为自由变量。

解释2:闭包指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。拿下图的装 饰器作为例子,内层函数averager的函数体中series就是一个列表的引用,但这个列表定义在了averager函数体外面,所以这就是个闭包,而变量series就是一个自由变量。自由变量指未在本地作用域中绑定的变量
闭包与变量
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。**如果一定要引用循环变量怎么办?**方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变。

比如使用lambda函数的时候会发生比较尴尬的情况 输出与预期极度不符合。
[lambda x: x*i for i in range(4)] 理解

在Python中,只有模块(module),类(class)以及函数(deflambda)才会引入新的作用域,
其它的代码块(如iftryfor等)是不会引入新的作用域的
if 2>1:
    x = 1
print(x)    #结果为 1 因为if语句没有引入作用域

defclasslambda是可以引入新作用域的:
def test():
    x = 2#因为x是局部作用域
print(x) #NameError: name 'x' is not defined

global关键字声明的变量必须在全局作用域上,不能嵌套作用域上,
# 当要修改嵌套作用域(enclosing作用域,外层非全局作用域)中的变量就需要nonlocal关键字了:
def outer():
    num = 25
    def inner():
        nonlocal num#nolocal声明保证了父级函数变量作用域子级函数作用域
        print(num)#如果没有nolocal声明 此语句会因为函数内部没有对num定义而输出失败 输出25
        num = 100
        print(num) #输出100
    #return inner() 不可以return语句结束 为什么不可以return结束
    inner()#注意此处函数的结束方式 如果不加此结束声明 函数为空
    print(num) #输出100
outer()#注意函数内部不传入参数时函数是怎么结束的

当内部作用域想修改外部作用域的变量时,
当修改的变量是在全局作用域(global作用域)上的,就要使用global先声明一下
count = 20
print(count)
def outer():
    global count#如果不加这句话会报错 UnboundLocalError: local variable 'count' referenced before assignment
    #global声明 保证了全局变量的可以作用于函数的局部作用域
    #count = count+1#如果不加全局声明 此语句会因为局部变量未定义而报错
    print(count)#这里不加全局变量声明会报错,因为后续全局量的值已经被修改
    #当全局变量的值在函数内部被修改时 函数内部必须加一个global声明
    count = 200
    print(count)#输出200
outer()#函数内部 加上全局变量声明之后 全局变量的值已经发生改变 输出200
print(count)#函数内部 加上全局变量声明之后 全局变量的值也会发生改变 输出200

体会上述代码与底下代码区别
count = 25
def outer():
    #print(count)#在global声明之前就调用了该函数 后续又对函数值进行修改 函数作用域混乱 
    #如果不进行函数值修改 是可以正常输出的
    global count
    count = count+5
    print(count)
outer()
print(count)

再有以下代码
count = 66
def outer():
    print(count)#输出66
outer()#必须得有这个结尾 否则函数无输出
count = count+22 #函数外全局作用域内改变变量值 无需加声明
print(count)#输出 88

再有以下代码
count = 20
def f():
    #global count
    count = count+1#报错 因为局部变量 无声明
    print(count)
f()

再有如下例子
def outer():
    x = 0
    def inner():
        #x = 1+x 改变变量的值会引起变量未定义的错误
        x = 1
        #同一变量重新赋值不会引起变量未定义的错误 #且重新定义的变量的值只作用于此函数内       
        print('inner x',x)
    #return inner #为什么此处不能以return结束函数
    inner()
    print('outer x',x)
#return outer()#为什么不能以return语句结束函数
outer()

闭包的解释2:
以下代码:

def closure_func():
    in_func = 'In Func'
    def f():
        return in_func
    return f
ff = closure_func()   # 此时ff=f
ff()  # 此时ff()=f(),函数f()访问了外部函数的局部变量in_func
'In Func'

此时的ff就是一个闭包,包括了f()函数和自由变量in_func
其实在ff = closure_func()语句结束后函数closure_func()生命周期就结束了,但是其局部变量in_func依然存在,因为它被闭包引用了,因此不会被回收。

如果在一个内部函数里,对在外部作用域(但不在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。定义在外部函数内的但由内部函数引用或者使用的变量被称为自由变量。

def make_power(x):
    def f(y):
        return y ** x
    return f   
power_two = make_power(2)   
power_two(5)
25
power_two(3)
9
可以看到make_power()函数结束后,依然存在,被闭包引用。
所有函数都有一个 __closure__属性,如果这个函数是一个闭包的话,那么它返回的是一个由
 cell 对象 组成的元组对象。cell 对象的cell_contents 属性就是闭包中的自由变量。
power_two.__closure__
(<cell at 0x7f9154476918: int object at 0xa68a60>,)
power_two.__closure__[0].cell_contents  #自由变量存储在了cell_contents中
2

闭包容易出现的错误
1、外部函数局部变量

闭包中函数无法修改外部函数的局部变量
要修改外部函数的局部变量,可是使用nonlocal关键字,修改上面的函数:

def out_func():
    out_var = 'Out Var'
    def inner_func():
        out_var = 'Inner Var'
        return out_var
    print('before: {} '.format(out_var))
    inner_func()
    print('after: {} '.format(out_var))
out_func()
输出
before: Out Var 
after: Out Var 

上述函数做修改

def out_func():
    out_var = 'Out Var'
    def inner_func():
        nonlocal out_var
        out_var = 'Inner Var'
        return out_var
    print('before: {} '.format(out_var))
    inner_func()
    print('after: {} '.format(out_var))
out_func()
输出
before: Out Var 
after: Inner Var 

2、 循环的问题

def count():
    fs = []
    for i in range(1, 4):
    #i在f()中被引用,f()为闭包函数,i为自由变量,在父级for循环函数结束后,i未得到释放,
    而是直接被子级函数f()所用
        def f():
             return i*i
        fs.append(f)
    return fs
f1, f2, f3 = count()
输出
f1() 输出9
f2() 输出9
f3() 输出9

修改函数如下:

def count():
    def f(j):
       return lambda: j*j
    fs = []
    for i in range(1, 4):
        fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
        #此时i不为自由变量 为局部变量 
    return fs
f1, f2, f3 = count()
f1() 输出1
f2() 输出4
f3() 输出9

再思考如下代码

flist = []
for x in range(1,3):
    def func():
        #global x#这样写是错误的 for循环没有作用域的概念
        nonlocal x #SyntaxError: no binding for nonlocal 'x' found nonlocal关键字是能作用域局部变量,当使用nonlocal声明变量 a 时,
                   #就会往上最近一层局部作用域寻找局部变量 a ,结果没找着,报错。
        return x*x
    flist.append(func)
for m in flist:
    print(m())

闭包的解释3
闭包的本质
先来看一下这段代码:

def outer():
  arg_1 = "我是外部函数的变量"

  def inner():
    print(arg_1)
    
  inner()
  ​
outer()
输出:
我是外部函数的变量

这里的arg_1是outer函数的局部变量,当我们调用outer函数的时候,内部函数inner也会被调用,arg_1的值被访问到并且打印出来,而当outer函数被调用结束过后,arg_1变量所占用的内存空间会被释放,就相当于从这个世界消失了,任何地方想要访问都不可能。这还不是闭包,但是已经离闭包很近了。

现在,我们把上面的代码的某一行变一下:

def outer():
  arg_1 = "我是外部函数的变量"
​​
  def inner():
    print(arg_1)
  return inner
​​
a = outer()
a()
输出为
我是外部函数的变量

仔细看看有什么区别,你会发现,我们把inner函数的引用,作为outer函数的返回值了,意思就是说,outer函数的返回值也是一个函数,这个函数就是内部函数inner。这,就是闭包。

你仔细看一下代码,会发现一个很奇怪的现象,a = outer()这行代码已经调用了outer函数,也就是说,outer函数已经执行完毕了,那是不是arg_1变量也应该随之消失呢,可是,当我执行a()这句代码时,发现还能打印出arg_1变量的值,这是违反常理的。这里面的关键就在于inner函数被作为返回值了,并且在inner函数中引用了引用了arg_1变量,把变量值inner函数包在了一起,也就是我们所说的闭包,这样,arg_1变量就脱离了原本创建它的函数(outer函数)而存在。

这里,再深入剖析一下闭包中,外部函数里定义的变量为什么能脱离外部函数而存在。
其实所有的函数,都有一个closure属性,如果这个函数是一个闭包的话,那么它返回的是一个由 cell 对象 组成的元组对象。cell 对象的cell_contents 属性就是闭包中的自由变量。

>>>test.__closure__
(<cell at 0x000001C388850A68: int object at 0x0000000074E96C40>,)
>>>test.__closure__[0].cell_contents
1

这下看明白了吧,其实也就是python把这个变量存起来了。

维基百科的解释:
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。

归纳一下,闭包必须满足的三个条件:
1、需要有一个内嵌函数(也就是上面例子中的inner函数)
2、内嵌函数需要引用定义在外部函数中变量(自由变量)
3、内嵌函数需要被返回(这一点最重要,是区别普通嵌套函数与闭包的本质)

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值