为什么在Python中修改父框架只对模块框架有效?

5 投票
1 回答
1427 浏览
提问于 2025-04-18 05:34

我在玩一个叫做 inspect.stack() 的东西。我尝试去修改父级框架中的一个局部变量,结果发现只有当父级框架是在模块级别时,这个修改才有效。下面的代码展示了这个情况(Python 2.7):

import inspect


def outer():
    a = 10 
    print a
    modify()
    print a


def modify():
    inspect.stack()[1][0].f_locals['a'] = 8888

outer()

a = 20 
print a
modify()
print a

那么,当父级框架是一个函数的时候,为什么就不行呢?我能让它工作吗?

1 个回答

6

模块的 f_locals 就是它的 globals()!你可以通过在 modify 函数里面打印 is 比较和 globals() 来验证这一点:

def modify():
    print(inspect.stack()[1][0].f_locals is globals())
    inspect.stack()[1][0].f_locals['a'] = 8888

做了这个修改后,输出是:

$python3 modify.py 
10
False
10
20
True
8888

修改 globals() 返回的字典确实有效(可以参考这个问题)。文档中明确说明模块是用一个简单的 dict 来实现它的命名空间:

模块是通过 import 语句导入的(见“导入语句”部分)。模块对象有一个由字典对象实现的命名空间(这是模块中定义的函数的 func_globals 属性所引用的字典)。

但是,locals() 返回的字典不一定是本地命名空间:

注意:这个字典的内容不应该被修改;更改可能不会影响解释器使用的本地和自由变量的值。

在某些版本的 Python 2 中,当在函数内部使用 exec 语句时,修改 locals() 是有效的。试着在 outer 函数里面加上 exec '',看看输出是否会改变(这并不保证会改变!但可能性更大)。

补充:在 Python 2.7.6 中,如果栈帧使用 exec 语句并且代码中没有对本地变量的赋值,我可以让它工作。例如,定义 outer 如下:

def outer():
    exec('a = 10')
    print(a)
    modify()
    print(a)
    locals()['a'] = 9999
    print(a)

我得到的输出是:

$python2 modify.py 
10
False
8888
9999
20
True
8888

但是如果我在 exec 后面加上 a = 10,像这样:

def outer():
    exec('a = 10')
    a = 10
    print(a)
    modify()
    print(a)
    locals()['a'] = 9999
    print(a)

结果是:

$python2 modify.py 
10
False
10
10
20
True
8888

我希望这能让你明白,给本地变量赋值的条件真的很,并且绝对不可靠。

据我所知,在 Python 3 中,locals() 返回的字典总是只是实际命名空间的一个副本,所以修改它永远不会有效。不过也没有保证。

f_locals 属性返回的值就是在那个时刻 locals() 返回的值。

总结一下:,没有可靠的方法可以修改通用栈帧的本地命名空间。你不能修改、删除或添加新的本地变量到通用栈帧的命名空间中。

撰写回答