Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assignment order matters unexpectedly with "exec expr in globals(), locals()"

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.

like image 635
dreeves Avatar asked Sep 18 '12 20:09

dreeves


2 Answers

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
like image 186
JBernardo Avatar answered Nov 20 '22 21:11

JBernardo


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

like image 29
Odomontois Avatar answered Nov 20 '22 23:11

Odomontois