zoukankan      html  css  js  c++  java
  • 洗礼灵魂,修炼python(23)--自定义函数(4)—闭包进阶问题—>报错UnboundLocalError: local variable 'x' referenced before assignment

    闭包(lexical closure)

    什么是闭包前面已经说过了,但是由于遗留问题,所以单独作为一个章节详解讲解下

    不多说,看例子:

    def funx(x):
        def funy(y):
            return x*y     #此时的funy函数对外层funx函数的变量调用,则称为闭包
        return funy

    结果:

    >>> i=funx(4)
    >>> i
    <function funx.<locals>.funy at 0x000000000331B7B8>
    >>> i(5)
    20
    >>> a=funx(4)(5)   #切不可写成a=funx(4(5))
    >>> a
    20

    再一例:

     

     

    但是呢,有个很严重的问题,并不是外层的变量都可以调用的,如果是这样写的话:

    def fun1():
        x=5
        def fun2():
            x*=x   
            return x
        return fun2

     结果:

     

    问题来了:UnboundLocalError: local variable 'x' referenced before assignment

    这是为嘛啊?看报错信息,提示的是本地变量x再调用前没有被定义,也就是说,fun2函数用的x并不是我们以为的fun1的变量,因为这里写法和上面的例子是不同的,那我改成一样的写法看看:

     

    还是不行,为嘛啊?

    我个人的理解是:在之前使用闭包是没有对传入的值进行改动的,是直接返回出来的,当我们传入4或者5时,传入这个举动就已经赋值定义好了X和y,这都是前一章参数的功劳。而后面的写法都是对传入的值有改变(x*=x),因为x指向的是一个不可变对象—常量,我们对传入的值已经改变,也就是内存ID发生改变,那么这个被改过的x*=3得到的X则是一个新的变量,在fun2函数空间内来说,这个X是没有被定义的,所以报错。而在之前我们只是声明了一个原始X的变量,所以返回时不报错

    官方解释是:由于python对变量的搜索机制引起,当在调用函数时,如果有一个函数在内部对外层函数的变量做赋值操作 ,该变量会被认为是本地的(这就是前面说的屏蔽),所以如果你修改变量的值就会把其变成局部变量,在修改的同时就会创建一个同名的局部变量来屏蔽,那么该变量的引用自然就会出现未定义,所以这就解释为何直接调用不报错,而赋值操作就会报错

    图解(个人理解):

     

     

    那么这种情况怎么解决呢?在以后的开发中,总有要重新赋值的操作吧?是的,解决方法是有的,而且是多个方法:

    方法一:使用nonlocal关键词

     

    那么再直接以原写法加nonlocal关键词看看:

     

    果然可行

    注意:nonlocal参数在python3中才有,python2里没有

    方法二:使用global关键词

    使用global参数有两种情况:

    1.已经有一个全局变量被定义,并且希望调用全局变量

    2.函数内外层都把对象定义为全局变量,并且希望调用外层函数的变量

    方法三:很“猥琐”的一招:建一个新的本地变量不要改动上层函数的变量

    是的,很猥琐,基本和外层函数没关系了,只不过值是一样的而已。顺便一说,虽然猥琐,但是确实是个办法,这样的操作在类定义里也会用到

    方法四:使用容器类型

    容器类型:如果某个对象包含对其他对象的引用,则将其称为容器。

    容器不是存放在栈(后进先出的则为栈)里面,不受屏蔽影响,变量不会被屏蔽掉。字符串,列表,元祖都是容器类型

     好,那么我们试一下看看:

    代码:

    def fun1():
        x=[5,6]
        def fun2():
            x[0]*=x[0]
            return x[0]
        return fun2

    结果是可行:

     

     

    那么这个方法就会带来一个新的问题,先看看例子再说:

     

    x并没有变对不对,这符合在前面说的,字符串,整形(常量)都是不可变对象,所以需要改则得重新赋值,这里要说明一下,变量本身是不可变对象,因为变量的赋值只是贴的一个标签而已,在前面的章节里说过的

    那么使用容器类型呢?

     

    果然是变了,因为list本身就是一个可变对象。但是说个很严重的问题,假如你在实际开发中,你的数据是list,这个数据很重要,你这样调用一次,list里的数据就变了,那么如果操作错或者说如果在测试中做测试,那岂不用一次少一次?这个时候,注意,你可以使用列表切片或者拷贝的方法来避免这种事发生

     

    最后做个总结

    • 内部函数,不修改全局变量可以访问全局变量
    • 内部函数,修改同名全局变量,则python会认为它是一个局部变量
    • 在内部函数修改同名全局变量并调用变量,则引发UnboundLocalError: local variable 'x' referenced before assignment

     

    看到这里,再结合前面的是不是有点懵X了?哈哈,没关系,多练习,多理解,很简单,你会看懂的

  • 相关阅读:
    [java]struts2入门
    [c#基础]ICloneable接口
    idea jsp html 空白页的问题
    在Intellij Idea中使用jstl标签库
    org.apache.catalina.LifecycleException: Failed to start component
    tomcat点击startup.bat一闪而退的方法
    [转]小心C# 5.0 中的await and async模式造成的死锁
    体验h5离线缓存
    [Asp.net core]使用Polly网络请求异常重试
    asp.net core读取appsettings.json,如何读取多环境开发配置
  • 原文地址:https://www.cnblogs.com/Eeyhan/p/7647807.html
Copyright © 2011-2022 走看看