• 陈发财
    2018-06-19
    闭包计数器为什么要用列表? 用整数变量为啥传不进嵌套函数呢?
    好像定义整数变量后,只要在嵌套函数里定义一下noblocal就可以了,这又是为啥……

    作者回复: 这个问题非常好!首先我们先从nonlocal说起,python在使用变量的时候要遵循一个LEGB规则,
    什么又是LEGB呢,就是如果你使用了一个函数内的变量做运算,python会从函数里面找这个变量的定义,如果找不到函数里面的定义就会报错了--即你看到的未定义先使用的错误了。
    这种实现方式比js要好,避免在你忘记声明局部变量的时候,误使用了全局变量。
    那变量都有几个作用域(影响范围)呢?一共是四个,分别是局部(local)-闭包(enclosing)-全局(global)-模块(builtin),LEGB就是取的他们的首字母;
    如果要引用的变量的定义没在内部函数里面,而是在闭包里面就可以通过nonlocal声明一下,python就会从外层函数里面找这个变量的定义了,如果使用了global关键字修饰内部函数的变量,运算时就会从全局变量里面找变量的定义了。

    上面说的是变量作用域,再来说下我为什么使用列表,这里使用列表的目的就是为了达到 nonlocal变量的功能,因为对列表的操作是直接操作内存的位置,对变量的操作是重新分配了一块新的内存;
    所以在内部函数直接使用列表的名称也是操作外部函数定义的列表。这里需要通过python底层执行过程观察:
    from dis import dis
    # dis模块可以反汇编python函数的字节码
    def counter2(first2=0):
        CNT2 = first2

        def add_one():
            # nonlocal CNT2
            CNT2 += 1
            return CNT2
        return add_one

    dis(counter2(3))


    如果将nonlocal注释掉会显示反汇编结果为
    LOAD_FAST 0 (CNT2)
    如果不注释nonlocal或使用列表会显示反汇编结果为
    LOAD_DEREF 0 (CNT2)

    LOAD_FAST 的含义是加载了本地变量
    LOAD_DEREF 的含义是加载了引用的变量
    LOAD_GLOBAL 的含义是加载了全局变量

    这里涉及到python作用域和底层的一些原理,希望你能通过尝试更多的示例进行学习总结

    
     14
  • BeanNan
    2018-07-02
    老师,我觉得你说的有些问题,如果闭包里面引用的变量的定义没有在闭包中,那么他也会去外层函数中去查找,直到查找到全局作用域,代码如下

        cnt2 = 1
        def counter():
            def add_one():
                a = cnt2 + 1
                print(a)
                return a
            return add_one
        
        add_one = counter()
        add_one()
        #output 2

    上述代码中输入2,说明在闭包中他是可以访问了全局的变量的,即使不用加nonlocal, 那么关于这段代码如下

        def counter():
            count = 1
            def add_one():
                count += 1 # error
                return count
            return add_one

    那么上述代码报错原因,我也上网搜了一下,一个我可以理解的解释是,在闭包中count += 1,实际上是重新声明了一个count变量,覆盖了外层函数的count变量,那么此时这个count变量也没有被赋值,却参与到了运算当中,那么就会报错了,我也用老师说的反汇编测试过

    老师,我这样理解的不知道对不对,请老师指点下,我用的python版本是3.7的
    展开

    作者回复: 很细心,第一个问题我在解释的时候不严谨,如果函数内只引用全局变量,但不修改时可以不使用global关键字,一旦对全局变量进行修改必须声明,如:在函数内使用cnt2 += 1 就会报错了,这种情况下强制使用 global cnt2 显式声明才可以正常使用
    def add_one():
        global cnt2 # 声明
        cnt2 += 1 # 对全局变量做修改
        a = cnt2 + 1
        print(a)
        return a

    第二个问题的理解是对的,能够正确理解变量作用域对工作中有非常大的帮助,继续加油!

    
     3
  • 张望
    2019-08-27
    为什么留言前两条在说一些看起来跟本课无关的术语,这些同学是学了其他语言过来的么- -他们提的问题我没想到也看不懂
    
     2
  • 白开水
    2020-02-02
    老师你好,我使用global变量进行了练习,为什么没有按照我传入的数字进行计数呢,还是按照初始赋值的变量的值进行计数,麻烦指点一下。
    a = 0
    b = 10
    def counter(x,y):
        def add_one():
            global a,b
            a += 1
            b += 1
            return a,b
        return add_one
    #老师为什么我传值之后不是按照2和20计数呢?
    num1 = counter(2,20)
    print (num1())
    print (num1())
    print (num1())
    print (num1())
    print (num1())
    print (num1())

    输出结果:为什么没按照我传入的2和20开始计数?
    (1, 11)
    (2, 12)
    (3, 13)
    (4, 14)
    (5, 15)
    (6, 16)
    展开
    
    
  • Serendipity
    2019-10-04
    cnt[0] += 1;列表可以和 int 类型做加法运算吗??

    作者回复: 要看 cnt[0] 是什么类型

    
    
  • Geek_b498b7
    2019-09-02
    def counter():
        cnt=[0]
        def add_one():
            cnt[0]+=1
            return cnt[0]
        return add_one
    # counter()
    num1=counter()
    print(counter()())
    print(counter()())

    print(num1())
    print(num1())

    为什么num1()可以累加而counter()()的结果一直是1?
    展开

    作者回复: print(counter()()) 形成了匿名函数的调用,因为没有引用,执行完成被python GC机制回收,所以没有+1

    
    
  • 我是,露莹
    2019-04-19
    老师您好,我对def counter(FIRST = 0):
                            cnt = [FIRST]
    这一部分有点小疑惑,为什么传参FIRST = 0之后,如果counter()函数有传值就使用FIRST值,没有传值就默认为0呢?
    这个FIRST = 0, 不是指将0赋值给FIRST,从此之后FIRST都等于0吗?

    烦请老师解答,谢谢。
    展开

    作者回复: 这里涉及函数执行的顺序问题,使用FIRST = 0作为参数,在定义函数时FIRST就得到了 0, 如果没有传值,那么FIRST就取得0了, 如果给函数传值,FIRST就会取得传递的值,覆盖掉0,FIRST就是新的值了

    
    
  • 吕显超
    2019-03-15
    老师,Python 是不可以返回表达式吗?下面这段代码是正确的,可以运行,但我改写一下就报错了

    # 正确的代码
    def counter():
        l = [0]

        def add_one():
            l[0] += 1
            return l[0]

        return add_one

    改写为如下:

    # 错误的代码

    def counter():
        l = [0]

        def add_one():
            return l[0] += 1

        return add_one

    报错如下:

      File "/Users/lxc/PycharmProjects/test/28.py", line 5
        return l[0] += 1
                     ^
    SyntaxError: invalid syntax
    展开

    作者回复: 函数的return 语句只能返回一个值,这个值可以是任意的类型,如果有需要返回多个值可以通过把他们组合成列表、元组、字典等方式返回

    
    
  • 陈光
    2018-11-17
    老师,您好。可以这样理解吗:
    num1 = counter()
    这条语句只是初始化了外部函数的列表及返回内部函数的引用,此时内部函数并没有执行
    之后num1()
    这条语句直接执行内部函数的语句并返回最终的结果值
    如果在外部函数中使用整型变量,会随着外部函数的调用结束整型变量的内存会被释放,从而造成内部变量无法引用其值;如果使用列表,则内存不会随着外部函数的调用结束而释放,内部函数可以继续引用其值。那么有个疑问:列表的内存是何时被释放的?整个程序执行结束吗?
    展开

    作者回复: 我简单说下,python如何释放内存呢?它使用了一个引用计数器的概念,引用不为0内存就不会释放。怎么监测到有多少个引用呢?
    from sys import getrefcount
    getrefcount(CNT)
    getrefcount(counter)

    自己尝试一下看能得到你想要的答案吗?

    
    
  • 硕杨Sxuya
    2018-09-27
    看了留言的解释和网上搜索到情况,整理一份理解:

    1. list (因为是内建函数,所以他)的作用域默认是全局。无论哪里进行了操作、处理,都是本身数据变更了。
    2-1. 整型等数据,需要先定义,然后才能使用;且默认的作用域是当前的局部。
    2-2. 如果想用整型进行“计数器”的实现,需要声明成 global 类型的变量。而且需要在函数外面就定义出来,不然就无法重置。
    (尝试后成功的 code 如下:)

    aCounter = 0
    def counter():
    ---def add_one():
    ------global aCounter
    ------aCounter += 1
    ------return aCounter
    ---return add_one
    展开

    作者回复: 正确,另外书写规范建议用4个空格来缩进代码(手机手打一样无奈中)

    
    
  • Switch
    2018-07-21
    闭包最初理解起来有点困难,特别是对写多了OOP代码的人。因为无法理解为啥要这么绕的去使用一个局部变量。
    但是,一旦理解了“作用域”这个概念。
    就能明白,无论是在面向过程开发中使用闭包,还是在面向对象编程中使用类。
    都是为了在某一作用域下共享资源。

    作者回复: 对的,因为面对初学者,我没有太理论化的解释作用域的问题。更多关于作用域的解释可以参考搜索引擎和我的其他留言回复。

    
    
我们在线,来聊聊吧