python小知识:闭包

python闭包

一、一切皆对象

在其他语言中(C#),函数只是一段可以执行的代码,并不是对象.但是在python中一切皆对象,我可以将数字,字符串赋值给a,也可以将一个函数赋值给一个变量。也可以将函数当成另外一个函数的参数传递到另外的函数里。

def a():
    pass

print(type(a))
<class 'function'>

二、什么是闭包

问可以在函数最外部调用curve吗?不能

def curve_pre():
    def curve():
        pass
# 但可以这样做
def curve_pre():
    def curve():
        print('this is a function')
    return curve
    
f = curve_pre()
f()
this is a function

但上面讲的并不是闭包,闭包的概念是和变量的作用域有关的

# 现在我们定义如下函数
def curve_pre():
    a = 25
    def curve(x):
        return a*x*x
    return curve
    
f = curve_pre()
f(2)
100
# 假设我在外面将a的值改变会发生什么 闭包!
def curve_pre():
    a = 25
    def curve(x):
        return a*x*x
    return curve  # 它不仅仅是吧curve函数给返回出来了,他讲函数与环境变量都返回出来了
    
a = 10  # 这个a其实是没用的
f = curve_pre()
f(2)
100

闭包 = 函数+环境变量(函数定义时候的外部变量而又不能是全局变量),将函数与环境变量做了封闭,注意那个a一定要是在curve的外部

print(f.__closure__)  # 闭包的环境变量由__closure__打印出来
print(f.__closure__[0].cell_contents)
(<cell at 0x000001B1423EE648: int object at 0x00007FF84B8CA490>,)
25

三、闭包的意义与深度理解

闭包的意义在于他保存的是一个环境,并不仅仅返回的是一个函数,如果你仅仅返回的是一个函数而没有返回他现场的环境变量的话,那这个函数的结果是非常容易受到外部环境变量的影响的,很难保证运行的正确性。

def f1():
    a = 10
    def f2():
        a = 20
        print('a1:', a)
    print('a2:', a)
    f2()
    print('a3:', a)  # 打印的是10,因为20会被认为局部变量
    
f1()
a2: 10
a1: 20
a3: 10

那么问题来了,上面那个函数是一个闭包吗?我们先将print语句删除,利用__closure__打印一下

def f1():
    a = 10
    def f2():
        a = 20
    f2()
    
f = f1()
print(f.__closure__)
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-12-bd51ac231b68> in <module>
      6 
      7 f = f1()
----> 8 print(f.__closure__)


AttributeError: 'NoneType' object has no attribute '__closure__'

此时会报错,为什么?因为f是一个None空值类型,为什么是一个空值,因为f1内部灭有返回任何结果,这个从语法上就是错误的,那么我们随意返回个值看一下

def f1():
    a = 10
    def f2():
        a = 20
    return a
    
f = f1()
print(f.__closure__)
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-13-208adc59245d> in <module>
      6 
      7 f = f1()
----> 8 print(f.__closure__)


AttributeError: 'int' object has no attribute '__closure__'

此时会报错,为什么?因为f是一个int类型,int类型也没有__closure__属性,还是不能说明他是不是一个闭包

def f1():
    a = 10
    def f2():
        a = 20
    return f2
    
f = f1()
print(f)
print(f.__closure__)
<function f1.<locals>.f2 at 0x000001B142485EE8>
None

为空,__closure__不存在,为什么呢?我们再改动一下,我们不要a=20了,看一下结果。

def f1():
    a = 10
    def f2():
        return a
    return f2
    
f = f1()
print(f)
print(f())
print(f.__closure__)
<function f1.<locals>.f2 at 0x000001B142485F78>
10
(<cell at 0x000001B1423EE1C8: int object at 0x00007FF84B8CA2B0>,)

可以惊喜的发现有值了,表面上我们仅仅是把爱a=20删掉了,但实质上如果有a=20这个语句在的话,那么此时的a将不再引用外部的环境变量了,而是被认为是此时的a是一个局部变量,被认为是局部变量之后,那么就跟外面的a没有关系了,他并没有引用环境变量,所以此时的闭包是不存在的。关键是a不能被当成一个变量去赋值,而是需要去引用外部的环境变量

四、利用闭包来解决问题

假如有一个旅行者,编写一个函数,得到旅行者当前的位置。其中初始值x = 0, 走3步,result=3,再走5步,result = 8
解决这个问题关键点是记录当前的结果

4.1 先不用闭包解决

origin = 0
def go(step):
    new_pos = origin + step
    origin = new_pos
    return origin

print(go(2))
print(go(3))
print(go(6))
---------------------------------------------------------------------------

UnboundLocalError                         Traceback (most recent call last)

<ipython-input-5-bf41cd89a19a> in <module>
      5     return origin
      6 
----> 7 print(go(2))
      8 print(go(3))
      9 print(go(6))


<ipython-input-5-bf41cd89a19a> in go(step)
      1 origin = 0
      2 def go(step):
----> 3     new_pos = origin + step
      4     origin = new_pos
      5     return origin


UnboundLocalError: local variable 'origin' referenced before assignment

想一下为什么会报错,这个代码的逻辑千疮百孔!不是说如果在函数内部有一个变量,而这个变量没有定义的话,他会往他的上一级去寻找,按道理是不应该报错的,为什么报错了?你可以试一下origin = new_pos删除,那么并不会报错了,那么这一句究竟有什么问题?

因为在函数内部使用了origin,然而在函数内部没有定义,那么他将沿着作用域链找到全局变量origin。但是在函数内部加上了origin = new_pos,那么python会认为出现在等号左边这样的变量是一个局部变量,python会认为函数内部是存在origin的,他将不会沿着作用域链找到外部的origin

也就是说现在函数内部origin是一个局部变量,那么可以将它变成全局变量吗?可以使用global关键字

origin = 0
def go(step):
    global origin
    new_pos = origin + step
    origin = new_pos
    return origin

print(go(2))
print(go(3))
print(go(6))
2
5
11

4.2 使用闭包方式实现

origin = 0

def factory(pos):
    
    def go(step):
        nonlocal pos  # 强制pos不是本地局部变量
        new_pos = pos + step
        pos = new_pos
        return new_pos
    return go
    
tourist = factory(origin)
print(tourist(2))
print(tourist.__closure__[0].cell_contents)  # 环境变量pos
print(tourist(3))
print(tourist.__closure__[0].cell_contents)
print(tourist(5))
print(tourist.__closure__[0].cell_contents)
2
2
5
5
10
10

这个闭包实现的关键点是pos成为了一个环境变量,环境变量是有一个保存现场的功能的,可以记忆中上一个状态

这就是一个函数编程,体验糟糕。但是其实比上一个方法要好,因为代码中要尽可能少的使用全局变量。

print(tourist.__closure__[0].cell_contents)
10





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值