4

In Python 2 (not sure about 3), the locals dictionary only gets updated when you actually call locals(). So e.g.

l=locals()
x=2
l['x']

fails because l doesn't have the key "x" in it, but

l=locals()
x=2
locals()
l['x']

returns 2.

I'm looking for a way to force an update of the locals dictionary, but the trick is that I'm in a different stack frame. So e.g. I'm looking to do

l=locals()
x=2
force_update()
l['x']

and I need to write the force_update() function. I know that from said function I can get the parent frame via inspect.currentframe().f_back, and even the parent (non-updated) locals via inspect.currentframe().f_back.f_locals, but how can I force an update?

If this seems convoluted, my main goal is to write a function which is shorthand for "{some} string".format(**dict(globals(),**locals())) so I don't have to type that out each time, and can instead do fmt("{some} string"). Doing so I run into the issue above.

Edit: With Martjin answer below, below is essentially the solution I was looking for. One could play around with exactly how they get the stack frame of the callee, here I do it via partial.

from functools import partial
from inspect import currentframe

fmt = partial(lambda s,f: s.format(**dict(globals(),**f.f_locals)),f=currentframe())
x=2
print fmt("{x}") #prints "2"
marius
  • 1,352
  • 1
  • 13
  • 29
  • In your editor you could define a shortcut, snippet, or whatever the editor calls it to expand to `.format(**dict(globals(),**locals()))`. A lot of editors support something like this. – Roland Smith Apr 17 '16 at 15:14
  • Why are you using `"{some} string".format(**dict(globals(),**locals()))` so many times in your code? Does your format string really need access to every variable in the local and global namespaces? – Thomas Nelson Apr 17 '16 at 15:15
  • If you can get the parent frame (see also `sys._getframe(1)`) then why do you need `globals()` and `locals()`? – cdarke Apr 17 '16 at 15:20

1 Answers1

2

Simply accessing f_locals on a frame object triggers the copy, so using inspect.currentframe().f_back.f_locals is enough.

See the frame_getlocals() function in the frameobject.c implementation:

static PyObject *
frame_getlocals(PyFrameObject *f, void *closure)
{
    PyFrame_FastToLocals(f);
    Py_INCREF(f->f_locals);
    return f->f_locals;
}

PyFrame_FastToLocals is the function used to copy the data from the interal array tracking locals values to a dictionary. frame_getlocals is used to implement the frame.f_locals descriptor (a property); see the frame_getsetlist definition.

The PyFrame_FastToLocalsWithError function used above is exactly what locals() uses to produce the same dictionary (by wrapping the PyEval_GetLocals function).

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Aha! Thanks. I managed to find `currentframe().f_locals` but not realize that it exactly forces the update I was looking for. – marius Apr 17 '16 at 15:22
  • Yes - jus beware that the opposite does not happen - there is no way to update the fast variables by changing the values in the locals. – jsbueno Apr 18 '16 at 23:57
  • @CharlieParker: I linked to the Python 3 source code in my answer; yes, it works in Python 3. – Martijn Pieters Oct 17 '17 at 06:48
  • @MartijnPieters wait si ur actually able to update locals and have things work? I've been looking for something like this with no luck at all. Why does ur approach work while others fail? – Charlie Parker Oct 17 '17 at 17:31
  • @CharlieParker: you can't add or update locals through the `locals()` dictionary (not via the `locals()` return value nor the `f_locals` reference), no. – Martijn Pieters Oct 17 '17 at 17:33
  • @MartijnPieters just out of curiosity, why is that not possible (even with your solution)? – Charlie Parker Oct 17 '17 at 17:34
  • 1
    @CharlieParker: see [Change the value of a local variable where variable name will be expressed as a string](//stackoverflow.com/a/32440576), Python heavily optimises the locals namespace in functions. – Martijn Pieters Oct 17 '17 at 17:46