7

The following code in Python 2.X prints "a : 2" as you'd expect:

def f():
  #a = 1
  exec "a = 2" in globals(), locals()
  for k,v in locals().items(): print k,":",v
  #a = 3

f()

But if you uncomment the "a = 1" then it prints "a : 1", as I didn't expect. Even weirder, if you uncomment the "a = 3" line then it doesn't print anything at all, which I definitely didn't expect (I had a baffling bug that I distilled down to that).

I think the answer is buried in the documentation on locals() and globals(), or maybe in other questions like this but I thought it was worth calling this manifestation out.

I'd love to learn what the Python interpreter is thinking here, as well as suggestions for workarounds.

Community
  • 1
  • 1
dreeves
  • 26,430
  • 45
  • 154
  • 229

2 Answers2

2

The old Python 2's exec would change the bytecode to search both the local and the global namespaces.

As you define the a = 2 in global, this is the one found when a = 1 is commented. When you uncomment a = 3, this is the a "found" but not defined yet.

If you read how symbol tables are processed in this great article by Eli Bendersky, you can better understand how local variables are processed.

You shouldn't use exec for this kind of code (I hope this is not production code) and it will break anyway when you port your code to Py3k:

Python 3's exec function is no longer a statement and, therefore, can't change the environment it is situated.


Probably I should go directly to the point:

If you are doing all this dynamic naming stuff, you should use a dictionary:

def f():
    data = {'a': 1}
    data['a'] = 2
    if ...:
        data['a'] = 3
JBernardo
  • 32,262
  • 10
  • 90
  • 115
  • Thanks! Any suggestions for workarounds? How would you do an eval that sets a local variable? – dreeves Sep 18 '12 at 21:01
  • 3
    @dreeves Use a dictionary instead: `data = {'a': 1}; data['a'] = 2` – JBernardo Sep 18 '12 at 21:03
  • Ironically I started with a dictionary but wanted to use the values in some messy expressions so I wanted to create a local variable for every key in the dict so I could say, for example, "a" instead of "data['a']". (So my hideous solution does accomplish that, as long as I don't assign to those local variables either before or after the execs!) – dreeves Sep 18 '12 at 21:13
  • 1
    @dreeves unfortunately you'll have to add a few keystrokes to each variable you dynamically change to make your code work as expected. `exec` is a tool to be used in very special cases. – JBernardo Sep 18 '12 at 21:16
1

from locals function documentation:

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

checking:

>>> def f():
    a = 1
    print(locals())
    locals()['a']=2
    print(a,locals())


>>> f()
{'a': 1}
1 {'a': 1}

So you just cant modify local context through the locals() function

Odomontois
  • 15,918
  • 2
  • 36
  • 71
  • You can't modify it directly, but `exec`-ed code can modify it. – BrenBarn Sep 18 '12 at 20:58
  • @BrenBarn It does only in python 2.X. But if context is specified explicitly it modifies the supplied dict only. In that case **locals()** is just copy of the local context and it's modification doesn't affect original. – Odomontois Sep 18 '12 at 21:02
  • 1
    @hayden And because of that there is the **Note**. Changes may or may not affect local variables. – Odomontois Sep 18 '12 at 21:06