Behavior of exec function in Python 2 and Python 3
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 functionexec()
returns.
Getting Python2 exec behaviour in python3: UnboundLocalError: local variable
Python thinks ec
is a local variable, which hasn't yet been assigned to, and you can't reference it before first assigning to it. Use the global
statement at the start of your function, like so:
def function_with_exec(table):
# ec now refers to the global variable, which presumedly already been as
global ec
(sess, tgt) = 3, None
try:
exec("ec = some_object.{}_get_entry_count(sess_hdl=sess, dev_tgt=tgt)".
format(table))
except AttributeError:
return []
ec = some_function(ec)
See 7.12. The global statement
The global statement is a declaration which holds for the entire current code block. It means that the listed identifiers are to be interpreted as globals. It would be impossible to assign to a global variable without global, although free variables may refer to globals without being declared global.
You could also remove the call to exec
by using getattr
instead:
def function_with_exec():
global ec
(sess, tgt) = 3, None
try:
ec = getattr(some_object, f"{table}_get_entry_count")(sess_hdl=sess,
dev_tgt=tgt)
except AttributeError:
return []
ec = some_function(ec)
Why does the exec() function work differently inside and outside of a function?
Pass the current local scope to the function and use that as the local dictionary for exec()
.
# example 2
def example_function(loc):
string_two = 'greeting_two = "hi"'
exec(string_two, loc)
print(greeting_two)
example_function(locals())
Read more here
Behavior of exec function in Python 2 and Python 3
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 functionexec()
returns.
What happend with exec in Python3.3?
Because exec()
is now a function, you can no longer use it to set local names in Python functions.
In Python 2, where exec
is a statement, the compiler could detect its use and disable the normal local name optimisations in place for functions.
Execute your code into a new dictionary instead:
namespace = {}
exec(logic, namespace)
l = namespace['Logic']()
Demo:
>>> logic = '''\
... class Logic:
... def Play(self, id, board):
... return id, board
... '''
>>> def Play(logic, board, id):
... namespace = {}
... exec(logic, namespace)
... l = namespace['Logic']()
... return l.Play(id, board)
...
>>> Play(logic, 'foo_board', 'bar_id')
('bar_id', 'foo_board')
Running exec inside function
It's going to damage your function's performance, as well as its maintainability, but if you really want to make your own code so much worse, Python2 (this will not work in Python3, there you need to use the second alternative) gives you "enough rope to shoot yourself in the foot" (;-):
>>> def horror():
... exec "x=23"
... return x
...
>>> print horror()
23
A tad less horrible, of course, would be to exec
in a specific dict:
>>> def better():
... d = {}
... exec "x=23" in d
... return d['x']
...
>>> print better()
23
This at least avoids the namespace-pollution of the first approach.
Related Topics
How to Filter Only Printable Characters in a File on Bash (Linux) or Python
Multi Platform Portable Python
Construct Pandas Dataframe from Items in Nested Dictionary
Checking Running Python Script Within the Python Script
I Have a Problem with Sending Mail:Typeerror: _Init_() Got an Unexpected Keyword Argument 'Context'
Use Df Command to Show Only the %Used
How to Run Celery Workers by Superuser
Change Parent Shell's Environment from a Subprocess
Creating a Symbolic in Shared Volume of Docker and Accessing It in Host MAChine
How to Install Writable Shared and User Specific Data Files with Setuptools
How to Send Http Requests to Flask Server
Get Rows Based on Distinct Values from One Column