修改`locals()`或`frame.f_locals`

时间:2016-01-07 08:48:49

标签: python cpython

我在这个问题上找到了一些模糊的相关问题,但没有为CPython找到任何干净而具体的解决方案。我认为“有效”的解决方案是特定的解释器。

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

  • locals()提供了一个不可修改的词典。
  • 一个函数可能(实际上)使用某种优化来访问其局部变量
  • frame.f_locals提供locals()字典,但不太容易通过exec来破解事物。或者至少我没有能力做像locals()['var'] = value ; exec ""
  • 这样的hackish无证件的事情
  • exec能够对局部变量做奇怪的事情,但它不可靠 - 例如。我在某处读到它在Python 3中不起作用。还没有测试过。

所以我理解,鉴于这些限制,额外变量添加到本地变量永远不会安全,因为它会破坏解释器结构。

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

我考虑的事情

  • 在功能f中,可以访问f.func_code.co_nlocalsf.func_code.co_varnames
  • 在一个框架中,可以通过frame.f_locals访问/检查/读取变量。这是通过sys.settrace设置跟踪器的用例。
  • 可以轻松访问框架所在的功能 - 查看设置跟踪的用例,并使用它来处理给定某个触发器或其他任何触发器的局部变量。

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

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

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

编辑:Hackish exec正在执行thisthis

等操作

2 个答案:

答案 0 :(得分:4)

它存在一个未记录的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,以避免在需求仅修改现有变量时出现间接。但是,由于这似乎主要是非文档化的API,源代码是文档资源。

答案 1 :(得分:2)

根据MariusSiuram的笔记,我写了一个显示行为的食谱。

结论是:

  1. 我们可以修改现有变量
  2. 我们可以删除现有变量
  3. 我们无法添加新变量。
  4. 所以,这是代码:

    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