3

In studying this answer, I discoved, much to my surprise, that exec has a strange behavior;

>>> def f1():
...     return x
... 
>>> def f2():
...     exec ""
...     return x
... 
>>> f1()
Traceback (most recent call last):
  ...
NameError: global name 'x' is not defined
>>> f2()
Traceback (most recent call last):
  ...
NameError: name 'x' is not defined
>>> x = 'bar'
>>> f1()
'bar'
>>> f2()
'bar'

Obviously, both return some global value x; but not so if f2() is changed slightly:

>>> def f2():
...     exec "x = 'im local now'"
...     return x
... 
>>> f2()
'im local now'

f2 returns its own special copy of x, even though there's nothing in the body of f2 that would seem to cause that (no assignment to x).

I can easy see how this is happening, the presence of the exec statement changes the LOAD_GLOBAL bytecode into a LOAD_NAME, in a similar vein as the presence of yield turns a function into a generator.

>>> dis.dis(f1)
  2           0 LOAD_GLOBAL              0 (x)
              3 RETURN_VALUE        
>>> dis.dis(f2)
  2           0 LOAD_CONST               1 ('')
              3 LOAD_CONST               0 (None)
              6 DUP_TOP             
              7 EXEC_STMT           

  3           8 LOAD_NAME                0 (x)
             11 RETURN_VALUE        

But what I don't understand is why. Is this documented behavior? is this an implementation detail of cpython? (it works in the same way in IronPython, although the dis module is non functional)

Community
  • 1
  • 1
SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
  • Your first `f2` also seems to be returning a local `x`. – arshajii Sep 06 '13 at 00:10
  • Doesn't this statement in the documentation of [exec](http://docs.python.org/3/library/functions.html?highlight=exec#exec) explain this behavior: "In all cases, if the optional parts are omitted, the code is executed in the current scope." – crayzeewulf Sep 06 '13 at 00:13

1 Answers1

3

Imagine a more general case:

def f(stuff):
    exec(stuff)
    return x

Python obviously has to use LOAD_NAME here, because it doesn't know whether the code in stuff will mess with x. The same case goes for any use of exec(), even if the argument is a constant — Python simply doesn't do a deep enough analysis to determine that any exec() happens to be safe.

(There's a perfectly good reason why it shouldn't do that analysis, too: the only case that it could even analyze with any degree of success would be exec() on a constant argument, which is pointless. If the argument is fully known ahead of time, it should just be normal code!)

  • 2
    `exec` isn't a function in Python 2. In Python 3, it's a function, but it doesn't affect the bytecode generated. – kindall Sep 06 '13 at 02:01