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

Mar*_*ram 5 python cpython

我发现了与此问题相关的一些模糊的问题,但没有找到任何针对CPython的干净且特定的解决方案。我认为“有效”解决方案是特定于解释器的。

首先,我认为我了解:

  • locals() 给出了不可修改的字典。
  • 函数可以(并且确实确实)使用某种优化来访问其局部变量
  • frame.f_locals给出了locals()类似的字典,但不太容易通过hackhacking exec。或至少我没有能力做些骇人听闻的未证明的事情,例如locals()['var'] = value ; exec ""
  • exec 能够对局部变量做一些奇怪的事情,但它并不可靠-例如,我读到某处它在Python 3中不起作用。未经测试。

因此,我了解到,鉴于这些限制,向局部变量添加额外的变量永远是不安全的,因为这会破坏解释器的结构。

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

我考虑过的事情

  • 在一种功能中f,可以访问f.func_code.co_nlocalsf.func_code.co_varnames
  • 在一个框架中,可以通过访问/检查/读取变量frame.f_locals。这是通过设置跟踪器的用例sys.settrace
  • 可以很容易地访问框架所在的功能-结合使用设置跟踪的用例,并使用它在给定触发器或任何给定局部变量的情况下“执行操作”。

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

如何从跟踪函数或修饰的包装函数或类似的东西实现对变量的修改?

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


编辑:hackish的exec是做这样的事情还是

Mar*_*ram 11

它存在一个未公开的 C-API 调用,用于执行以下操作:

PyFrame_LocalsToFast

这篇 PyDev 博客文章中有更多讨论。基本思想似乎是:

import ctypes

...

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

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

请注意,Fast如果要求只是修改现有变量,则可能有某种方法可以直接访问,以避免间接访问。但是,由于这似乎主要是未记录的 API,源代码是文档资源。


小智 7

根据 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()
Run Code Online (Sandbox Code Playgroud)

输出如下:

- 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
Run Code Online (Sandbox Code Playgroud)