Peter Otten wrote: > Steven D'Aprano wrote: > >> I have to dynamically generate some code inside a function using exec, >> but I'm not sure if it is working by accident or if I can rely on it. >> >> Here is a trivial example: >> >> >> py> def spam(): >> ... exec( """x = 23""" ) >> ... return x >> ... >> py> spam() >> 23 >> >> >> (My real example is more complex than this.) >> >> According to the documentation of exec, I don't think this should >> actually work, and yet it appears to. The documentation says: >> >> 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. >> >> http://docs.python.org/3.4/library/functions.html#exec >> >> >> I *think* this means that if I want to guarantee that a local variable x >> is created by exec, I need to do this instead: >> >> py> def eggs(): >> ... mylocals = {} >> ... exec( """x = 23""", globals(), mylocals) >> ... x = mylocals['x'] >> ... return x >> ... >> py> eggs() >> 23 >> >> The fact that it works in spam() above is perhaps an accident of >> implementation? Yes no maybe? > > eggs() should work in Python 2 and 3, > spam() should work in Python 2, but not in Python 3. > > Fun fact: Python 2 tweaks the bytecode (LOAD_NAME instead of LOAD_GLOBAL) > to make spam() work: > >>>> def spam(): > ... return x > ... >>>> dis.dis(spam) > 2 0 LOAD_GLOBAL 0 (x) > 3 RETURN_VALUE >>>> def spam(): > ... exec "" > ... return x > ... >>>> dis.dis(spam) > 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
Some more bytcode fun, because it just occured to me that you can optimize away the code that triggered the modification: >>> def spam(): ... return x ... if 0: exec "" ... >>> dis.dis(spam) 2 0 LOAD_NAME 0 (x) 3 RETURN_VALUE -- https://mail.python.org/mailman/listinfo/python-list