文章目录
- 参考
- 描述
- locals() 函数
- 双面间谍:locals()
- 静态 locals()
- 获取最新变化
- 保持同步
- 动态 locals()
- For Loop 过程中产生的迭代变量
- 遍历 locals() 时可能产生的 RuntimeError
- RuntimeError
- 异常产生原因
- 解决方案
- copy 方法
- 绕过 RuntimeError
- 产生 RuntimeError 异常的基本要求
- 遍历 globals() 时必不可少的 RuntimeError
参考
描述
locals() 函数
在 Python 中,locals()
是一个 内置函数
,用于返回 当前作用域的命名空间的字典
,该字典存储了当前作用域中定义的所有全局变量和函数。
locals()
函数返回一个字典,其中包含局部作用域中的变量名及其对应的值。这个字典可以用于 访问、修改和删除
当前作用域中的变量。
对此,请参考如下示例:
# 当在全局作用域中调用 locals() 函数时,
# 该函数将返回全局命名空间字典,与 globals()
# 返回的值相同。
print(locals() == globals())def func():# 当在函数中调用 locals() 函数时,# 该函数将返回其所在函数的局部命名空间字典。local_var = 'Hello World'print(locals())func()
执行效果
True
{'local_var': 'Hello World'}
双面间谍:locals()
静态 locals()
局部作用域下的 locals()
返回的是与局部作用域相关联的命名空间字典,该字典是静态的。这意味着,字典中的内容并不与实际命名空间中的变化保持同步。对此,请参考如下示例:
def func():first_locals = locals()# 此时局部命名空间中已注册 first_locals 变量# 但该变量并不会体现在 first_locals 中print(first_locals)a = 1# 此时局部命名空间中已注册 a 变量# 但该变量并不会体现在 first_locals 中print(first_locals)func()
执行效果
{}
{}
获取最新变化
在 Python 中,你可以通过在局部作用域中调用 locals()
来更新与局部命名空间相关联的字典,使其内容与局部命名空间中的内容保持一致。对此,请参考如下示例:
def func():first_locals = locals()print(first_locals)a = 1print(first_locals)# 调用 locals() 以更新与局部命名空间# 相关联的字典。locals()print(first_locals)func()
执行效果
{}
{}
{'first_locals': {...}, 'a': 1}
保持同步
为了时刻获取当前状态下局部命名空间中的内容,你应该总是使用 locals()
函数来获取与局部命名空间相关联的字典,而不是使用 locals()
的赋值结果。对此,请参考如下示例:
def func():print(locals())a = 1print(locals())func()
执行效果
{}
{'a': 1}
动态 locals()
全局作用域下的 locals()
返回的是与全局作用域相关联的命名空间字典,该字典是动态的(这一点与 Python 提供的 globals()
内置函数是相同的)。这意味着,字典中的内容时刻与实际命名空间中的变化保持同步。对此,请参考如下示例:
first_locals = locals()# 判断全局变量 a 是否存在于 first_locals 中
print('a' in first_locals)# 定义全局变量 a
a = 1print('a' in first_locals)
执行效果
False
True
For Loop 过程中产生的迭代变量
在 Python 的 for 循环
中,迭代变量
是指在每次迭代中 用于表示可迭代对象中的当前元素的变量
。迭代变量的名称是根据程序员的选择来确定的,它可以是 任意有效的变量名
。
举个栗子
# 在下面的 For Loop 中,迭代变量为 num
for num in range(100):pass# 在 For Loop 过程中,将会在当前作用域下
# 创建迭代变量。因此在 For Loop 结束后,
# 仍能够在当前作用域中访问到该变量。
print(num)
执行效果
99
遍历 locals() 时可能产生的 RuntimeError
RuntimeError
当你在 Python 的局部作用域中尝试遍历 locals()
函数的返回结果时,可能会产生 RuntimeError
。对此,请参考如下示例:
def func():china = 'Hello China'for i in locals():locals()func()
执行效果
执行上述代码后,将产生 RuntimeError
异常错误。其中包含如下内容
RuntimeError: dictionary changed size during iteration
异常产生原因
字典在内部使用 散列表(也被称为哈希表)
来实现 键值对
的 存储
和 查找
。散列表的 工作原理
涉及将键通过哈希函数转换为索引,然后在存储桶中存取相应的值。
散列表的结构在 迭代过程
中是 敏感的
。当你在迭代字典的 视图对象(字典提供的视图对象是动态的、实时反映字典内容变化的可迭代对象)
时,若 添加
或 删除
字典中的条目,会导致 字典的结构发生变化
。
当你迭代字典提供的视图对象时,迭代器会维护一个 内部状态
来跟踪字典的条目。如果在迭代过程中修改字典的大小,迭代器的内部状态与字典的实际状态将不再一致
。如果继续迭代,可能会产生意想不到的结果或错误的行为。为了保持迭代的一致性和正确性,Python 选择在这种情况下抛出 RuntimeError
异常,以提醒开发者字典发生了变化,并防止继续迭代可能导致的问题。
在迭代 locals()
函数时,可能将引发 RuntimeError
。其原因是,在迭代过程中产生的迭代变量将注册至当前作用域中,若在循环体中调用 locals()
,使得 locals()
字典更新,将导致被迭代字典对象的结构发生改变,从而引发了 RuntimeError
异常错误。
如果被迭代对象为 locals()
,并且你未在循环体中调用 locals()
函数,则被迭代对象的结构将不会由于迭代变量的产生而发生改变,因此 Python 也不会抛出 RuntimeError
异常。对此,请参考如下示例:
def func():china = 'Hello China'for i in locals():# 此处不调用 locals() 以更新被迭代字典对象passfunc()
执行效果
执行过程中,未产生任何异常错误。
解决方案
copy 方法
在 Python 中,字典对象具有 copy()
方法,用于创建字典的 浅拷贝
。
字典对象的 copy()
方法用于创建一个 新
的字典对象,其中包含原始字典的副本。该副本是一个独立的字典对象,具有相同的键值对。但是,它是一个 浅拷贝
,意味着副本中的键值对是原始字典中相应键值对的 引用
。
举个栗子
d = {'member': ['RedHeart', 'BinaryMoon']}# 获取字典对象 d 的浅拷贝
shallow_d = d.copy()# 判断两个字典中的键值对的内容是否完全相同,
# 不考虑键值对的排列顺序。
print(d == shallow_d)# 判断原对象与其浅拷贝对象的身份标识是否相同,
# 对象的身份标识及其在内存空间中存放的内存地址信息。
print(d is shallow_d)# 尝试修改字典中的可变对象(列表)
d['member'].append('BlackOvercoat')# 由于浅拷贝后的数据结构中的元素是原数据结构的元素的引用,
# 因此可变对象的更改将影响其他浅拷贝中相应可变对象的更改。
print(d)
print(shallow_d)
执行效果
True
False
{'member': ['RedHeart', 'BinaryMoon', 'BlackOvercoat']}
{'member': ['RedHeart', 'BinaryMoon', 'BlackOvercoat']}
绕过 RuntimeError
在迭代过程中产生的迭代变量将注册至当前作用域的命名空间中。若在局部作用域中迭代 locals()
函数,将在局部命名空间中注册迭代变量,这会导致 locals()
函数发生改变,从而引发 RuntimeError
。若我们迭代的是 locals()
的副本,即使迭代过程中产生的迭代变量可能会导致原对象发生改变,但这并不会影响到其相关的拷贝对象。
举个栗子
def func():china = 'Hello China'for i in locals().copy():locals()func()
执行效果
遍历对象由 locals()
改为 locals().copy()
后,执行上述代码,Python 将不会抛出 RuntimeError
异常错误。
产生 RuntimeError 异常的基本要求
在遍历 locals()
产生 RuntimeError
的一个基本要求是,在开始对 locals()
函数进行首次遍历时,locals()
函数的返回结果中存在 至少一个
的键值对。第一次迭代过程中将产生并在当前作用域下注册 迭代变量(Python 将从被迭代对象中取出一个值并赋值给迭代变量,若没有从被迭代对象中取出值则迭代变量不会被创建)
。
def func():for i in locals():# 由于首次遍历 locals() 时,# locals() 中不存在任何内容,因此# 循环体中的内容不会被执行,# 迭代变量也不会在当前作用域中注册。print('Hello World')locals()func()
执行效果
上述代码执行后,控制台中未输出任何内容。
遍历 globals() 时必不可少的 RuntimeError
在 Python 中,globals()
是一个 内置函数
,用于返回 全局作用域的命名空间的字典
,该字典存储了全局作用域中定义的所有对象。
globals()
函数返回一个字典,其中包含局部作用域中的变量名及其对应的值。这个字典可以用于 访问、修改和删除
当前作用域中的变量。
globals()
与 locals()
函数的功能类似,这也意味着 globals()
也存在与 locals()
相同的问题,但 globals()
相比 locals()
来说,RuntimeError
异常触发的机制存在不同。如需对其进行了解,欢迎前往我的另一篇博客 遍历 globals() 时必不可少的 RuntimeError。