一直以为各种语言的作用域差不多,在遇到各种python奇怪的编码方式之后,探查python的变量作用域,才发现与C++ java等大不相同。看了一些资料后,做些笔记。

一篇非常全面的博客: https://www.jianshu.com/p/17a9d8584530

上面博客已经总结的非常好了,主要有以下几点。

1. 变量作用域

与C++ java等不同,python中 if-elif-else, if-else, while(), try-finally, try-except等关键字语句中没有作用域,只有在Module(模块), class(类), def(函数)中定义的变量,才有作用域。

2. 作用域的类型

作用域有四种类型,局部(local)作用域, 嵌套(enclosing)作用域, 全局(global)作用域,内置(built-in)作用域。
local: 包含在def语句块中,也就是函数中定义的变量。每次函数被调用时都会创建一个新的命名空间(以词典的形式实现,在以后的python中可能会改变)。在递归调用时,每次调用自己也仍然会创建一个新的命名空间。如果没有使用global, nonlocal关键字声明,会默认创建局部变量。函数调用创建,函数返回时销毁。

关于命名空间,可查看官方文档 https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces

enclosing: 也定义在关键字def语句块中。但指函数中嵌套函数,类中嵌套类的情况。相当于上级作用域。
global: 在模块层次中定义的变量,每个.py文件都相当于一个模块。从外部来看,每个模块的全局变量都相当于模块对象的属性。
built-in: 系统内固定模块的变量,如定义在built-in模块中的变量。其生命周期是python解释器运行时开始,python解释器退出时结束。

3. 作用域的使用方式

虽然作用域定义是静态的,但在使用时是动态的。也就是说,在作用域中查找变量,会自底向上逐层查找。查找的次序是:global - enclosing - global - builtin. 如果未找到,则报NameError的错误。
在开头链接的博客中,有详细准确的例子,推荐逐个仔细看。

4. 不同作用域变量的修改

在实际使用中很容易因为这条犯错。在local作用域中没找到,转而向上层寻找到的变量,在此时的local作用域中是read-only, 如果尝试对其进行修改,将会直接创建一个同名的局部变量,而global变量不受影响。如下情况:

ins =  23
def test():
	ins = 24
	print(ins)

test()
print(ins)

此时的输出结果是:

24
23

如果想在局部作用域中修改全局变量,可以加上gloabl, nonlocal关键字。

global ins 
ins = 23
def test():
	global ins 
	ins = 24
	print(ins)

test()
print(ins)

输出结果:

24
24