20

locals is a built in function that returns a dictionary of local values. The documentation says:

Warning

The contents of this dictionary should not be modified; changes may not affect the values of local variables used by the interpreter.

Unfortunately, exec has the same problem in Python 3.0. Is there any way round this?

Use Case

Consider:

@depends("a", "b", "c", "d", "e", "f")
def test():
    put_into_locals(test.dependencies)

depends stores the strings provided in its arguments in a list test.dependences. These strings are keys in a dictionary d. I would like to be able to able to write put_into_locals so that we could pull the values out of d and put them into the locals. Is this possible?

martineau
  • 119,623
  • 25
  • 170
  • 301
Casebash
  • 114,675
  • 90
  • 247
  • 350

5 Answers5

17

I just tested exec and it works in Python 2.6.2

>>> def test():
...     exec "a = 5"
...     print a
...
>>> test()
5

If you are using Python 3.x, it does not work anymore because locals are optimized as an array at runtime, instead of using a dictionary.

When Python detects the "exec statement", it will force Python to switch local storage from array to dictionary. However since "exec" is a function in Python 3.x, the compiler cannot make this distinction since the user could have done something like "exec = 123".

http://bugs.python.org/issue4831

To modify the locals of a function on the fly is not possible without several consequences: normally, function locals are not stored in a dictionary, but an array, whose indices are determined at compile time from the known locales. This collides at least with new locals added by exec. The old exec statement circumvented this, because the compiler knew that if an exec without globals/locals args occurred in a function, that namespace would be "unoptimized", i.e. not using the locals array. Since exec() is now a normal function, the compiler does not know what "exec" may be bound to, and therefore can not treat is specially.

Unknown
  • 45,913
  • 27
  • 138
  • 182
  • I think it is pretty conclusive that it is just not possible – Casebash Sep 20 '09 at 05:35
  • 2
    @Casebash, it probably is possible, it just requires byte code hacks or Python 2.x – Unknown Sep 20 '09 at 05:49
  • @Casebash: you might not want to hold your breath. Python byte codes are not very well documented. – Unknown Sep 20 '09 at 18:07
  • I'll probably look at it myself some day. ATM, I really am not going to get enough utility out of it to justify the effort – Casebash Sep 21 '09 at 00:55
  • 1
    The problem is not that the Interpreter would unnecessarily flinch from optimizing after `exec=123`; it is that it would trust the innocent-looking `print("hello=world")` even after `print=eval`. – tiwo Feb 05 '13 at 22:23
  • is there a built in way without using `exec`? – Charlie Parker Oct 16 '17 at 22:21
  • does it mean that this can't work even with the `exec` statement? because I tried it with `exec` and even though the variable exists, it can't find it later in the script... – Charlie Parker Oct 16 '17 at 23:03
8

The local variables are modified by assignment statements.

If you have dictionary keys which are strings, please don't also make them local variables -- just use them as dictionary keys.

If you absolutely must have local variables do this.

def aFunction( a, b, c, d, e, f ):
    # use a, b, c, d, e and f as local variables

aFunction( **someDictWithKeys_a_b_c_d_e_f )

That will populate some local variables from your dictionary without doing anything magical.

S.Lott
  • 384,516
  • 81
  • 508
  • 779
  • just what I was thinking; you could also dynamically create a function; see help(types.FunctionType) – gatoatigrado Oct 04 '09 at 23:34
  • 7
    This is an interesting idea. However, there are many applications in which the dictionary actually contains many other variables (that are not needed by `aFunction()`), which makes the current definition of `aFunction()` break. A useful generalization is: `aFunction(a, b, c, d, e, f, **kwargs)`. – Eric O. Lebigot Jul 18 '10 at 10:40
  • @EOL: Extra parameter variables make a function break? That's hard to imagine. A few extra variables should be -- well -- just variables. A function that breaks because of a few extra variables has a remarkably poor design. It would be better to fix this function. – S.Lott Jul 18 '10 at 12:01
  • 4
    @S. Lott: Let me rephrase my point: what breaks is having the signature `def aFunction(a, b, c, d, e, f)` when `someDictWithKeys_a_b_c_d_e_f` contains more keys than these few variables, which is a typical situation when performing complex scientific calculations (the whole calculation uses more variables than most of the functions it calls). As I pointed out, `def aFunction(a, b, c, d, e, f, **kwargs)` is a convenient way of addressing this situation. – Eric O. Lebigot Jul 18 '10 at 18:01
  • @EOL: I can't see how this "breaks". There are simply extra variables which are unused. How is that "broken"? – S.Lott Jul 19 '10 at 14:54
  • 4
    @S. Lott: just run the code of your answer (which I upvoted) with `aFunction(**{'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4, 'f': 5, 'g': 6})` and this will precisely show why my remark can be useful to StackOverflow readers. In real-life scientific calculations, dictionaries typically hold more variables than what is sent individually to each function called. – Eric O. Lebigot Jul 20 '10 at 08:24
  • @EOL: "Typically"? That's just bad design. "Breaks"? That's just bad design. Are you suggesting that bad design can be tolerated? Is that your definition of "breaks"? "has a design that's fatally flawed"? I think "breaks" is the wrong word. I think "supports proper debugging" might be what you're talking about. I cannot seriously imagine software so poorly written that "typically" functions have the wrong set of arguments provided to them. – S.Lott Jul 20 '10 at 09:56
  • 4
    @S. Lott: The whole purpose of this is to get more convenient access to dict or object members. The idea is that some formula like `(a**2+b)*exp(c*d/e)` is easier to read than `(n.a**2+n.b)*exp(n.c*n.d/n.e)` for some `n` (perhaps just wrapping the dict containing the relevant variables). As for "bad design", suppose you have some parameters that determine the shape of a collection of functions. For example, these could be material parameters describing the equation of state and other properties for a gas. – Jed May 30 '11 at 17:39
  • 4
    Each function uses a subset of the parameters and you don't want to write a bunch of boilerplate to extract only the part that is really needed. You don't want to modify the callers when the function needs more parameters and you don't want to modify the inner functions when a different part of the model requires a new parameter to be introduced. – Jed May 30 '11 at 17:39
6

This isn't possible. I think this is to allow for performance optimizations later on. Python bytecode references locals by index, not by name; if locals() was required to be writable, it could prevent interpreters from implementing some optimizations, or make them more difficult.

I'm fairly certain you're not going to find any core API that guarantees you can edit locals like this, because if that API could do it, locals() wouldn't have this restriction either.

Don't forget that all locals must exist at compile-time; if you reference a name that isn't bound to a local at compile-time, the compiler assumes it's a global. You can't "create" locals after compilation.

See this question for one possible solution, but it's a serious hack and you really don't want to do that.

Note that there's a basic problem with your example code:

@depends("a", "b", "c", "d", "e", "f")
def test():
    put_into_locals(test.dependencies)

"test.dependencies" isn't referring to "f.dependencies" where f is the current function; it's referencing the actual global value "test". That means if you use more than one decorator:

@memoize
@depends("a", "b", "c", "d", "e", "f")
def test():
    put_into_locals(test.dependencies)

it'll no longer work, since "test" is memoize's wrapped function, not depends's. Python really needs a way to refer to "the currently-executing function" (and class).

Community
  • 1
  • 1
Glenn Maynard
  • 55,829
  • 10
  • 121
  • 131
2

I would store it in a variable:

refs    = locals()
def set_pets():
    global refs
    animals = ('dog', 'cat', 'fish', 'fox', 'monkey')
    for i in range(len(animals)):
        refs['pet_0%s' % i] = animals[i]

set_pets()
refs['pet_05']='bird'
print(pet_00, pet_02, pet_04, pet_01, pet_03, pet_05 )
>> dog fish monkey cat fox bird

And if you want to test your dict before putting it in locals():

def set_pets():
    global refs
    sandbox = {}
    animals = ('dog', 'cat', 'fish', 'fox', 'monkey')
    for i in range(len(animals)):
        sandbox['pet_0%s' % i] = animals[i]
    # Test sandboxed dict here
    refs.update( sandbox )

Python 3.6.1 on MacOS Sierra

0

I'm not sure if it is subject to the same restrictions, but you can get a direct reference to the current frame (and from there, the local variables dictionary) through the inspect module:

>>> import inspect
>>> inspect.currentframe().f_locals['foo'] = 'bar'
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'foo', 'inspect']
>>> foo
'bar'
dcrosta
  • 26,009
  • 8
  • 71
  • 83
  • 8
    This is exactly the same as locals(); `inspect.currentframe().f_locals is locals()` is true. – Glenn Maynard Sep 20 '09 at 04:21
  • 4
    This is not **totally** wrong, but it only works when the frame is the topmost one i.e. the global scope. It wont work within local scopes. – bendtherules Jun 12 '15 at 01:50