本篇文章由 VeriMake 旧版论坛中备份出的原帖的 Markdown 源码生成
原帖标题为:关于命名空间 | Python 的命名空间
原帖网址为:https://verimake.com/topics/249 (旧版论坛网址,已失效)
原帖作者为:dawdler(旧版论坛 id = 64,注册于 2020-08-17 14:01:22)
原帖由作者初次发表于 2021-08-04 00:59:27,最后编辑于 2021-08-04 00:59:27(编辑时间可能不准确)
截至 2021-12-18 14:27:30 备份数据库时,原帖已获得 227 次浏览、1 个点赞、0 条回复
Python
解读Python的命名空间
1. 命名空间
1.1 命名空间的作用与层级关系
"A namespace is a mapping from names to objects. Most namespaces are currently implemented as Python dictionaries."
以上是官方文档中的一段话——命名空间被用来记录变量的轨迹,在Python中通常以字典的形式呈现。
在任意一个Python程序中,都存在以下三种命名空间:
- Local namespace,局部命名空间:每个函数(或类)都有自己的局部命名空间,记录着函数的参数和函数内定义的变量;
- Global namespace,全局命名空间:每个模块都有自己的全局命名空间,记录模块内的函数、类、其它被引入的模块、模块级的变量与常量;
- Built-in namespace,内置命名空间:Python语言内置的命名空间,记录着Python的内置的函数与异常,所有模块都可以访问。
存在不同命名空间就意味着,虽然在同一个空间内不能有同名的变量或函数,但它们可以在不同的命名空间内各自存在。
1.2 命名空间的查找顺序
在Python中,当程序要使用一个变量x
的值时,解释器会
(a.) 首先搜索当前局部命名空间(local),也就是在当前这个函数体或类中搜索,如果有,就停止搜索,如果没有找到,就——
(b.) 搜索当前全局命名空间(global),也就是当前的模块,如果还没有搜索到,就——
(c.) 假设x
是内置函数或变量,搜索内置命名空间(built-in),如果依旧没有搜索到,解释器会停止搜索并报出NameError异常,也就是该变量x
没有被定义。
如果遇到嵌套函数,也是先搜索当前函数的命名空间,再搜索父函数的命名空间,然后搜索模块和内置命名空间,遵从由内而外的原则。
1.3 命名空间的生命周期
不同的命名空间在不同的时间被创建,它们的生命周期也各不相同:
- 内置命名空间在Python解释器启动时就被自动创建,且会一直被保留;
- 模块的全局命名空间是在模块定义被读入时创建的,通常情况下也会保留到解释器退出;
- 函数的局部命名空间会在该函数返回结果或抛出异常时结束,并被删除。
2. 作用域
"A scope is a textual region of a Python program where a namespace is directly accessible. "Directly accessible" here means that an unqualified reference to a name attempts to find the name in the namespace."
作用域就是一个Python程序可以直接访问命名空间的正文区域。也就是说,在Python中,我们定义的变量不是在任何位置都可以访问的,访问的权限取决于这个变量是在哪里赋值的。当我们在代码中定义了新的模块、类或函数时,就相当于引入了新的作用域。
Python的作用域分为4种,搜索顺序由内向外依次是:
- Local(L):最里层作用域,比如一个函数或方法的内部区域,包含局部变量;
- Enclosing(E):包含一些既不是局部的又不是全局的变量(non-local and non-global),比如一组嵌套函数,函数A里面包含了函数B,那么对于B中的名称来说,A中的作用域就是非局部的;
- Global(G):指当前脚本的最外层,通常是当前模块的全局空间;
- Built-in(B):包含了内置空间的变量与关键字等,也是在最后被搜索。
##global
x = 0
def func_A():
##enclosing
y = 10
def func_B():
##local
z = 100
##定义在内层的变量是外层无法直接访问的,
##也就是说我们无法在func_B的定义外直接调用z,
##也无法在func_A的定义外直接访问x。
如果想要查看内置作用域中预定义的变量,可以在终端中输入以下指令:
>>> import builtins
>>> dir(builtins)
3. 相关函数与关键字
3.1 使用globals()
与locals()
函数
因为在Python中,命名空间里的变量是用字典形式保存的,所以locals()'与'globals()
函数返回的就是其命名空间中保存了“变量名:实际值”对(pair)的字典。
def func_A():
x = 10
y = "100"
z = [1,2,3]
print(locals())
func_A()
print("-----")
a = 222
b = "hi"
def func_B():
w = 123
v = 456
print(globals())
以上代码的输出结果为:
{'x': 10, 'y': '100', 'z': [1, 2, 3]}
-----
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7fc28d06ebe0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/usercode/file.py', '__cached__': None, 'func_A': <function func_A at 0x7fc28d0721f0>, 'a': 222, 'b': 'hi', 'func_B': <function func_B at 0x7fc28cf79e50>}
从以上输出结果中可以观察到,locals()
函数只会返回局部命名空间内定义的变量,而globals()
函数不仅返回了在模块中定义的变量与函数,还有一些自动导入到模块中的内容,比如内置命名builtin。
此外,还有一个要注意的就是,locals()
函数返回的字典是只读的,也就是我们无法通过类似于locals()["x"] = 100"这样的语句来更改局部命名空间里的变量值,但
针对globals()`是可以的。
3.2 使用global
和nonlocal
关键字
有时候我们也会想要在内层作用域定义或修改外部作用域的变量,这时候就需要用到global
和local
关键字:
x = 1
def fun_A():
global x ##使用global关键字声明
print(x)
x = 100
print(x)
fun_A()
print(x)
print("-----")
##其中,如果是嵌套函数,
##即想要在内层函数func_B中修改外层函数func_A中定义的变量,
##可以将global关键字替换为nonlocal
def func_A():
y = 10
def func_B():
nonlocal y ##使用nonlocal关键字声明
y = 100
print(y)
func_B()
print(y)
func_A()
以上代码的输出结果为:
1
100
100
-----
100
100
Reference:
https://www.runoob.com/python3/python3-namespace-scope.html
https://www.cnblogs.com/windlaughing/archive/2013/05/26/3100362.html