I looked through the myriad ‘Python exec’ threads on SO, but couldn’t find one that answered my issue. Terribly sorry if this has been asked before. Here’s my problem:
# Python 2.6: prints 'it is working'
# Python 3.1.2: "NameError: global name 'a_func' is not defined"
class Testing(object):
def __init__(self):
exec("""def a_func():
print('it is working')""")
a_func()
Testing()
# Python 2.6: prints 'it is working'
# Python 3.1.2: prints 'it is working'
class Testing(object):
def __init__(self):
def a_func():
print('it is working')
a_func()
Testing()
As the standard function definition works in both Python versions, I’m assuming the problem must be a change to the way exec works. I read the API docs for 2.6 and 3 for exec and also read the “What’s New In Python 3.0” page and couldn’t see any reason why the code would break.
You can see the generated bytecode for each Python version with:
And, for each interpreter:
As you can see, Python 3.2 searches for a global value (LOAD_GLOBAL) named
a_funcand 2.7 first searches the local scope (LOAD_NAME) before searching the global one.If you do
print(locals())after theexec, you’ll see thata_funcis created inside the__init__function.I don’t really know why it’s done that way, but seems to be a change on how symbol tables are processed.
BTW, if want to create a
a_func = Noneon top of your__init__method to make the interpreter know it’s a local variable, it’ll not work since the bytecode now will beLOAD_FASTand that don’t make a search but directly gets the value from a list.The only solution I see is to add
globals()as second argument toexec, so that will createa_funcas a global function an may be accessed by theLOAD_GLOBALopcode.Edit
If you remove the
execstatement, Python2.7 change the bytecode fromLOAD_NAMEtoLOAD_GLOBAL. So, usingexec, your code will always be slower on Python2.x because it has to search the local scope for changes.As Python3’s
execis not a keyword, the interpreter can’t be sure if it’s really executing new code or doing something else… So the bytecode don’t change.E.g.
tl;dr
exec('...', globals())may solve the problem if you don’t care the result being added to global namespace