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

It exists an undocumented C-API call for doing things 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.


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

Tags:

Python

Cpython