修改 `locals()` 或 `frame.f_locals` 中的 *existing* 变量

Modify *existing* variable in `locals()` or `frame.f_locals`

我发现了一些与此问题相关的模糊问题,但没有针对 CPython 的任何干净且具体的解决方案。我假设 "valid" 解决方案是特定于解释器的。

首先是我认为我理解的事情:

所以我明白,考虑到这些限制,向局部变量添加 额外变量永远不会安全,因为它会破坏解释器结构。

但是,应该可以更改已经存在的变量,不是吗?

我考虑过的事情

变量应该在某个地方,最好是可写的...但我找不到它。即使它是一个数组(为了解释器的高效访问),或者我需要一些额外的 C 特定布线,我也准备好接受它。

我怎样才能从跟踪函数或装饰包装函数或类似的东西实现对变量的修改?

一个完整的解决方案当然会受到赞赏,但即使是一些指针也会对我有很大帮助,因为我被困在这里有很多不可写的字典:-/


编辑:Hackish exec 正在做类似 this or this

的事情

它存在一个未记录的C-API要求做这样的事情:

PyFrame_LocalsToFast

this PyDev blog post 中还有一些讨论。基本思路好像是:

import ctypes

...

frame.f_locals.update({
    'a': 'newvalue',
    'b': other_local_value,
})
ctypes.pythonapi.PyFrame_LocalsToFast(
    ctypes.py_object(frame), ctypes.c_int(0))

我还没有测试这是否按预期工作。

请注意,如果要求只是修改现有变量,可能有一些方法可以直接访问 Fast,以避免间接访问。但是,由于这似乎主要是 non-documented API,源代码是文档资源。

根据 MariusSiuram 的笔记,我写了一个食谱来展示这种行为。

结论是:

  1. 我们可以修改现有变量
  2. 我们可以删除现有变量
  3. 我们不能添加新变量。

所以,这是代码:

import inspect
import ctypes

def parent():
    a = 1
    z = 'foo'

    print('- Trying to add a new variable ---------------')
    hack(case=0)  # just try to add a new variable 'b'
    print(a)
    print(z)
    assert a == 1
    assert z == 'foo'

    try:
        print (b)
        assert False  # never is going to reach this point
    except NameError, why:
        print("ok, global name 'b' is not defined")

    print('- Trying to remove an existing variable ------')
    hack(case=1)
    print(a)
    assert a == 2
    try:
        print (z)
    except NameError, why:
        print("ok, we've removed the 'z' var")

    print('- Trying to update an existing variable ------')
    hack(case=2)
    print(a)
    assert a == 3


def hack(case=0):
    frame = inspect.stack()[1][0]
    if case == 0:
        frame.f_locals['b'] = "don't work"
    elif case == 1:
        frame.f_locals.pop('z')
        frame.f_locals['a'] += 1
    else:
        frame.f_locals['a'] += 1

    # passing c_int(1) will remove and update variables as well
    # passing c_int(0) will only update
    ctypes.pythonapi.PyFrame_LocalsToFast(
        ctypes.py_object(frame),
        ctypes.c_int(1))

if __name__ == '__main__':
    parent()

输出如下:

- Trying to add a new variable ---------------
1
foo
ok, global name 'b' is not defined
- Trying to remove an existing variable ------
2
foo
- Trying to update an existing variable ------
3