Python3的变量作用域规则和nonlocal关键字(2)

如果你还是不放心,那么我们再来看看字节码:

> dis(f) 3 0 LOAD_GLOBAL 0 (print) 2 LOAD_GLOBAL 1 (i) 4 CALL_FUNCTION 1 6 POP_TOP 4 8 LOAD_CONST 1 ('a') 10 STORE_GLOBAL 1 (i) 5 12 LOAD_GLOBAL 0 (print) 14 LOAD_GLOBAL 1 (i) 16 CALL_FUNCTION 1 18 POP_TOP 20 LOAD_CONST 0 (None) 22 RETURN_VALUE

对于i的存取已经由LOAD_GLOBAL和STORE_GLOBAL接手了,没问题。

当然global也有它的局限性:

一旦声明global,那么这个名字始终是global作用域的一个变量,不可以再是局部变量

名字必须存在于global里,因为python在运行时进行名字查找,所以你的变量在global里找不到的话对它的引用将会出错

接上一条,因为global限定了名字查找的范围,所以像闭包作用域的变量就找不到了

事实上需要引用非global名字的需求是极其常见的,因此为了解决global的不足,python3引入了nonlocal

使用nonlocal声明闭包作用域变量

假设我们有一个需求,一个函数需要知道自己被调用了多少次,最简单的实现就是使用闭包:

def closure(): count = 0 def func(): # other code count += 1 print(f'I have be called {count} times') return func

还是老问题,这样写对吗?

答案是不对,你又制造暂时性死区啦!

>>> f=closure() >>> f() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in func UnboundLocalError: local variable 'count' referenced before assignment

这时候就要nonlocal出场了,它声明一个名字位于闭包作用域中,如果闭包作用域中未找到就报错。

所以修正后的函数如下:

def closure(): count = 0 def func(): # other code nonlocal count count += 1 print(f'I have be called {count} times') return func

测试一下:

>>> f=closure() >>> f() I have be called 1 times >>> f() I have be called 2 times >>> f() I have be called 3 times >>> f2=closure() >>> f2() I have be called 1 times

现在可以正常使用和修改闭包作用域的变量了。

总结

当然,在函数里修改外部变量往往会导致潜在的缺陷,但有时这样做又是对的,所以希望你在好好了解作用域规则的前提下合理地利用它们。

作用域规则可以总结为下:

名字查找按照LEGB规则进行,如果当前代码在global中则从global作用域开始查找,否则从local开始

builtin作用域中是内置类型和函数,所以它们总是能被找到,前提是不要在局部作用域中对它们赋值

global中存放着所有定义在当前模块和导入的名字

local是局部作用域,存放在形成局部作用于的代码中有赋值行为的名字

闭包作用域是闭包函数的外层作用域,里面可以存放一些自定义的状态

global声明一个名字在global作用域中

nonlocal声明一个名字在闭包作用域中

最重要的一条,当你在能产生局部作用域的代码中对一个名字进行赋值,那么这个名字就会被认为是一个local作用域的变量从而屏蔽其他作用域中的同名对象

只要记住这些规则你就可以和因作用域引起的各种问题说再见了。而且理解了这些规则还会为你探索更深层次的python打下坚实的基础,所以请将它牢记于心。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/2a721ed1ce93a1f31b145caaf6945874.html