Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Behavior of exec function in Python 2 and Python 3

Following code gives different output in Python2 and in Python3:

from sys import version  print(version)  def execute(a, st):     b = 42     exec("b = {}\nprint('b:', b)".format(st))     print(b) a = 1. execute(a, "1.E6*a") 

Python2 prints:

2.7.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)] ('b:', 1000000.0) 1000000.0 

Python3 prints:

3.2.3 (default, Apr 11 2012, 07:15:24) [MSC v.1500 32 bit (Intel)] b: 1000000.0 42 

Why does Python2 bind the variable b inside the execute function to the values in the string of the exec function, while Python3 doesn't do this? How can I achieve the behavior of Python2 in Python3? I already tried to pass dictionaries for globals and locals to exec function in Python3, but nothing worked so far.

--- EDIT ---

After reading Martijns answer I further analyzed this with Python3. In following example I give the locals() dictionay as d to exec, but d['b'] prints something else than just printing b.

from sys import version  print(version)  def execute(a, st):     b = 42     d = locals()     exec("b = {}\nprint('b:', b)".format(st), globals(), d)     print(b)                     # This prints 42     print(d['b'])                # This prints 1000000.0     print(id(d) == id(locals())) # This prints True a = 1. execute(a, "1.E6*a")  3.2.3 (default, Apr 11 2012, 07:15:24) [MSC v.1500 32 bit (Intel)] b: 1000000.0 42 1000000.0 True 

The comparison of the ids of d and locals() shows that they are the same object. But under these conditions b should be the same as d['b']. What is wrong in my example?

like image 414
Holger Avatar asked Feb 26 '13 09:02

Holger


People also ask

What does exec () do in Python?

exec() in Python. Exec function can dynamically execute code of python programs. The code can be passed in as string or object code to this function. The object code is executed as is while the string is first parsed and checked for any syntax error.

What does Python exec return?

What does exec return in Python? Python exec() does not return a value; instead, it returns None. A string is parsed as Python statements, which are then executed and checked for any syntax errors. If there are no syntax errors, the parsed string is executed.


2 Answers

There is a big difference between exec in Python 2 and exec() in Python 3. You are treating exec as a function, but it really is a statement in Python 2.

Because of this difference, you cannot change local variables in function scope in Python 3 using exec, even though it was possible in Python 2. Not even previously declared variables.

locals() only reflects local variables in one direction. The following never worked in either 2 or 3:

def foo():     a = 'spam'     locals()['a'] = 'ham'     print(a)              # prints 'spam' 

In Python 2, using the exec statement meant the compiler knew to switch off the local scope optimizations (switching from LOAD_FAST to LOAD_NAME for example, to look up variables in both the local and global scopes). With exec() being a function, that option is no longer available and function scopes are now always optimized.

Moreover, in Python 2, the exec statement explicitly copies all variables found in locals() back to the function locals using PyFrame_LocalsToFast, but only if no globals and locals parameters were supplied.

The proper work-around is to use a new namespace (a dictionary) for your exec() call:

def execute(a, st):     namespace = {}     exec("b = {}\nprint('b:', b)".format(st), namespace)     print(namespace['b']) 

The exec() documentation is very explicit about this limitation:

Note: The default locals act as described for function locals() below: modifications to the default locals dictionary should not be attempted. Pass an explicit locals dictionary if you need to see effects of the code on locals after function exec() returns.

like image 119
Martijn Pieters Avatar answered Sep 20 '22 22:09

Martijn Pieters


I'd say it's a bug of python3.

def u():     exec("a=2")     print(locals()['a']) u() 

prints "2".

def u():     exec("a=2")     a=2     print(a) u() 

prints "2".

But

def u():     exec("a=2")     print(locals()['a'])     a=2 u() 

fails with

Traceback (most recent call last):   File "<stdin>", line 1, in <module>   File "<stdin>", line 3, in u KeyError: 'a' 

--- EDIT --- Another interesting behaviour:

def u():     a=1     l=locals()     exec("a=2")     print(l) u() def u():     a=1     l=locals()     exec("a=2")     locals()     print(l) u() 

outputs

{'l': {...}, 'a': 2} {'l': {...}, 'a': 1} 

And also

def u():     l=locals()     exec("a=2")     print(l)     print(locals()) u() def u():     l=locals()     exec("a=2")     print(l)     print(locals())     a=1 u() 

outputs

{'l': {...}, 'a': 2} {'l': {...}, 'a': 2} {'l': {...}, 'a': 2} {'l': {...}} 

Apparently, the action of exec on locals is the following:

  • If a variable is set within exec and this variable was a local variable, then exec modifies the internal dictionary (the one returned by locals()) and does not return it to its original state. A call to locals() updates the dictionary (as documented in section 2 of python documentation), and the value set within exec is forgotten. The need of calling locals() to update the dictionary is not a bug of python3, because it is documented, but it is not intuitive. Moreover, the fact that modifications of locals within exec don't change the locals of the function is a documented difference with python2 (the documentation says "Pass an explicit locals dictionary if you need to see effects of the code on locals after function exec() returns"), and I prefer the behaviour of python2.
  • If a variable is set within exec and this variable did not exist before, then exec modifies the internal dictionary unless the variable is set afterwards. It seems that there is a bug in the way locals() updates the dictionary ; this bug gives access to the value set within exec by calling locals() after exec.
like image 45
LRGH Avatar answered Sep 19 '22 22:09

LRGH