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

时光总嘲笑我的痴心妄想 提交于 2020-04-10 08:21:06

问题


I have found some vaguely related questions to this question, but not any clean and specific solution for CPython. And I assume that a "valid" solution is interpreter specific.

First the things I think I understand:

  • locals() gives a non-modifiable dictionary.
  • A function may (and indeed does) use some kind of optimization to access its local variables
  • frame.f_locals gives a locals() like dictionary, but less prone to hackish things through exec. Or at least I have been less able to do hackish undocumented things like the locals()['var'] = value ; exec ""
  • exec is capable to do weird things to the local variables, but it is not reliable --e.g. I read somewhere that it doesn't work in Python 3. Haven't tested.

So I understand that, given those limitations, it will never be safe to add extra variables to the locals, because it breaks the interpreter structure.

However, it should be possible to change a variable already existing, isn't it?

Things that I considered

  • In a function f, one can access the f.func_code.co_nlocals and f.func_code.co_varnames.
  • In a frame, the variables can be accessed / checked / read through the frame.f_locals. This is in the use case of setting a tracer through sys.settrace.
  • One can easily access the function in which a frame is --cosidering the use case of setting a trace and using it to "do things" in with the local variables given a certain trigger or whatever.

The variables should be somewhere, preferably writeable... but I am not capable of finding it. Even if it is an array (for interpreter efficient access), or I need some extra C-specific wiring, I am ready to commit to it.

How can I achieve that modification of variables from a tracer function or from a decorated wrapped function or something like that?

A full solution will be of course appreciated, but even some pointers will help me greatly, because I'm stuck here with lots of non writeable dictionaries :-/


Edit: Hackish exec is doing things like this or this


回答1:


It exists an undocumented C-API call for doing thigs like that:

PyFrame_LocalsToFast

There is some more discussion in this PyDev blog post. The basic idea seems to be:

import ctypes

...

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

I have yet to test if this works as expected.

Note that there might be some way to access the Fast directly, to avoid an indirection if the requirements is only modification of existing variable. But, as this seems to be mostly non-documented API, source code is the documentation resource.




回答2:


Based on the notes from MariusSiuram, I wrote a recipe that show the behavior.

The conclusions are:

  1. we can modify an existing variable
  2. we can delete an existing variable
  3. we can NOT add a new variable.

So, here is the code:

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()

The output would be like:

- 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


来源:https://stackoverflow.com/questions/34650744/modify-existing-variable-in-locals-or-frame-f-locals

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!