作者回复: 这个问题非常好!首先我们先从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作用域和底层的一些原理,希望你能通过尝试更多的示例进行学习总结
作者回复: 很细心,第一个问题我在解释的时候不严谨,如果函数内只引用全局变量,但不修改时可以不使用global关键字,一旦对全局变量进行修改必须声明,如:在函数内使用cnt2 += 1 就会报错了,这种情况下强制使用 global cnt2 显式声明才可以正常使用
def add_one():
global cnt2 # 声明
cnt2 += 1 # 对全局变量做修改
a = cnt2 + 1
print(a)
return a
第二个问题的理解是对的,能够正确理解变量作用域对工作中有非常大的帮助,继续加油!
作者回复: 要看 cnt[0] 是什么类型
作者回复: print(counter()()) 形成了匿名函数的调用,因为没有引用,执行完成被python GC机制回收,所以没有+1
作者回复: 这里涉及函数执行的顺序问题,使用FIRST = 0作为参数,在定义函数时FIRST就得到了 0, 如果没有传值,那么FIRST就取得0了, 如果给函数传值,FIRST就会取得传递的值,覆盖掉0,FIRST就是新的值了
作者回复: 函数的return 语句只能返回一个值,这个值可以是任意的类型,如果有需要返回多个值可以通过把他们组合成列表、元组、字典等方式返回
作者回复: 我简单说下,python如何释放内存呢?它使用了一个引用计数器的概念,引用不为0内存就不会释放。怎么监测到有多少个引用呢?
from sys import getrefcount
getrefcount(CNT)
getrefcount(counter)
自己尝试一下看能得到你想要的答案吗?
作者回复: 正确,另外书写规范建议用4个空格来缩进代码(手机手打一样无奈中)
作者回复: 对的,因为面对初学者,我没有太理论化的解释作用域的问题。更多关于作用域的解释可以参考搜索引擎和我的其他留言回复。