Python命名空间的本质与加载顺序详解
本文深入解析Python命名空间的本质、加载与查找顺序、生命周期及访问方式。详细阐述局部、全局及内置命名空间的层级关系,解析变量查找的LEGB规则,并通过locals()和globals()函数演示如何访问及修改命名空间,揭示两者在读写权限上的关键差异。
理解Python的命名空间是掌握Python编程核心机制的关键。深入研习命名空间,有助于从本质上厘清Python中那些看似琐碎却至关重要的运行规则。
本文将分四个部分深入剖析Python命名空间的本质:首先定义命名空间,其次解析其查找顺序,接着探讨生命周期,最后通过内置函数locals()和globals()来观察和访问命名空间的具体内容。
本文的重点在于第四部分,我们将通过该部分详细观察命名空间内部的具体构成与内容。
一、命名空间概述
Python利用“命名空间”这一机制来追踪变量的位置。本质上,命名空间是一个字典(dictionary)结构,其中键(key)对应变量名,值(value)对应变量的具体数值或对象。
A namespace is a mapping from names to objects. Most namespaces are currently implemented as Python dictionaries。
在任何一个Python程序的执行过程中,同时存在多个可用的命名空间。
1、每个函数都拥有独立的局部命名空间,用于记录该函数内部的变量,包括传入的参数以及函数内部定义的局部变量。
2、每个模块拥有独立的全局命名空间,用于记录模块层面的变量,涵盖函数定义、类定义、其他导入的模块、模块级变量以及常量。
3、此外还存在内置命名空间,它对所有模块可见,内部存放着Python的内置函数和异常类。
名称空间的加载顺序
内置命名空间(在程序运行前加载)→ 全局命名空间(在程序运行中,从上到下逐步加载)→ 局部命名空间(在程序运行中,仅在函数调用时才加载)。
二、名称空间的取值顺序
在局部作用域进行变量查找时,顺序为:局部命名空间 → 全局命名空间 → 内置命名空间。
在全局作用域进行变量查找时,顺序为:全局命名空间 → 内置命名空间。
综上所述,Python在查找变量时,遵循从小范围到大范围、逐层向上查找的原则。
三、命名空间查找顺序详解
当代码行需要使用变量x的值时,Python会遍历所有可用的名字空间进行查找,具体顺序如下:
1、局部命名空间:特指当前函数或类的方法。如果函数内部定义了局部变量x,或者x是函数的参数,Python将直接使用它,并停止后续搜索。
2、全局命名空间:特指当前所在的模块。如果模块中定义了名为x的变量、函数或类,Python将使用它并停止搜索。
3、内置命名空间:对每个模块都是全局可见的。作为最后的备选方案,Python会假设x是内置函数或变量。
4、如果Python在上述所有名字空间中均未找到x,它将放弃查找并抛出NameError异常,例如:NameError: name 'aa' is not defined。
嵌套函数的情况:
1、首先在当前(嵌套函数或lambda函数)的命名空间中搜索。
2、然后在父函数的命名空间中搜索。
3、接着在模块(全局)命名空间中搜索。
4、最后在内置命名空间中搜索。
示例:
info = "Adress : "
def func_father(country):
def func_son(area):
city= "Shanghai " #此处的city变量,覆盖了父函数的city变量
print(info + country + city + area)
city = " Beijing "
#调用内部函数
func_son("ChaoYang ");
func_father("China ")
输出:Adress : China Shanghai ChaoYang
在上述示例中,info变量位于全局命名空间,country变量位于父函数的命名空间,而city和area变量则位于自身函数的命名空间中。
四、命名空间的生命周期
不同的命名空间在不同的时刻创建,并拥有各自不同的生存期。
1、内置命名空间在Python解释器启动时创建,并将一直保留,直至解释器关闭,不会被删除。
2、模块的全局命名空间在模块定义被读取时创建,通常该命名空间会一直保存到解释器退出。
3、当函数被调用时,会创建一个局部命名空间;当函数返回结果或抛出异常时,该局部命名空间会被删除。需要注意的是,每一次递归调用都会拥有自己独立的命名空间。
Python的一个显著特点是,其赋值操作总是在最内层的作用域中进行。赋值操作并不会复制数据,而是将命名绑定到对象上。删除操作同理:“del y”仅仅是从局部作用域的命名空间中移除命名y。事实上,所有引入新命名的操作都作用于局部作用域。
示例:
i=1 def func2(): i=i+1 func2(); #错误:UnboundLocalError: local variable 'i' referenced before assignment
由于在创建命名空间时,Python会预先检查代码并填充局部命名空间。在Python执行该行代码之前,解释器就已经发现了对i的赋值操作,并将其添加到局部命名空间中。当函数实际执行时,Python解释器认为i存在于局部命名空间中但尚未赋值,因此会引发错误。
def func3(): y=123 del y print(y) func3() #错误:UnboundLocalError: local variable 'y' referenced before assignment #去掉"del y"语句后,运行正常
五、命名空间的访问
1、局部命名空间可以通过内置函数locals()进行访问。
locals返回一个包含名字/值对的字典(dictionary)。该字典的键是字符串形式的变量名,值则是变量的实际值。
示例:
def func1(i, str ): x = 12345 print(locals()) func1(1 , "first")
输出:{'str': 'first', 'x': 12345, 'i': 1}
2、全局(模块级别)命名空间可以通过内置函数globals()进行访问。
示例:
import copy
from copy import deepcopy
gstr = "global string"
def func1(i, info):
x = 12345
print(locals())
func1(1 , "first")
if __name__ == "__main__":
print("the current scope's global variables:")
dictionary=globals()
print(dictionary)
输出:(此处为了阅读方便,我对输出进行了人为换行和顺序调整,加颜色的语句将在下文重点说明)
{
'__name__': '__main__',
'__doc__': 'Created on 2013-5-26',
'__package__': None,
'__cached__': None,
'__file__': 'E:\\WorkspaceP\\Test1\\src\\base\\test1.py',
'__loader__': <_frozen_importlib.SourceFileLoader object at 0x01C702D0>,
'copy': <module 'copy' from 'D:\\Python33\\lib\\copy.py'>,
'__builtins__': <module 'builtins' (built-in)>,
'gstr': 'global string',
'dictionary': {...},
'func1': <function func1 at 0x01C6C540>,
'deepcopy': <function deepcopy at 0x01DB28A0>
}
总结
1、模块的名字空间不仅包含模块级的变量和常量,还包含在模块中定义的所有函数和类。此外,它还包含任何被导入到该模块中的内容。
2、我们可以看到,内置命名空间也被包含在一个模块中,它被称为__builtin__。
3、回顾一下from module import和import module之间的区别。
使用import module语句时,模块自身被导入,但它保持自己的名字空间。这就是为什么您需要使用模块名来访问其函数或属性(即module.function)的原因。
但是,使用from module import function语句时,实际上是将指定的函数和属性从另一个模块直接导入到您自己的名字空间中。这就是为什么您可以直接访问它们而无需引用来源模块的原因。使用globals函数,您可以真切地看到这一过程的发生,请参见上述输出中的红色高亮语句。
3、locals与globals之间的一个重要区别
locals返回的是只读视图,而globals不是。
示例:
def func1(i, info):
x = 12345
print(locals())
locals()["x"]= 6789
print("x=",x)
y=54321
func1(1 , "first")
globals()["y"]= 9876
print( "y=",y)
输出:
{'i': 1, 'x': 12345, 'info': 'first'}
x= 12345
y= 9876
解释:
locals实际上并没有返回局部名字空间本身,而是返回了一个副本。因此,对locals返回结果进行修改,不会影响局部名字空间中变量的实际值。
globals返回的是实际的全局名字空间,而不是副本。因此,对globals返回的字典进行的任何修改,都会直接反映到全局变量上。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









