I want to assign list to variable, while list
and variable
are both stored in a dictionary measurements
. This is the Python 2 version of measurements
with less variables:
measurements = {'tg': [8.184e+16, 8.345e+16, 8.045e+16, 8.520e+16, 8.322e+16, 7.622e+16, 4.305e+16, 2.203e+16]}
def f():
for key,val in measurements.items():
exec(key + ' = val') in globals(),locals()
print (tg)
f()
However, as mentioned in another question, it's not suitable for Python 3. If I write the code like this:
measurements = {'tg': [8.184e+16, 8.345e+16, 8.045e+16, 8.520e+16, 8.322e+16, 7.622e+16, 4.305e+16,
2.203e+16]}
def f():
for key,val in measurements.items():
ldict = {}
exec(key + ' = val', globals(),ldict)
key = ldict[key]
# exec(key + ' = val') in globals(),locals()
print (tg)
f()
I got this error: NameError: name 'val' is not defined
The ldict = {}
trick creates a substitute local namespace for use inside exec
. This is useful because the dict returned by locals()
doesn't write through to your actual locals like it used to in Python 2.
But that substitute namespace {}
is empty. It doesn't contain your locals()
, therefore it doesn't have val
in it. Try using ldict = {**locals()}
instead to copy the contents of your locals to the substitute locals ldict
.
Remember that you have to read all the "locals" created by exec from the ldict. Thus, print(tg)
won't work either, because it was only ever assigned in one of the substitute local namespaces. You probably don't want to make a new one every loop. Just .update()
one you make in advance.
def f():
ldict = {}
for key,val in measurements.items():
ldict.update(locals())
exec(key + ' = val', globals(),ldict)
key = ldict[key]
# exec(key + ' = val') in globals(),locals()
print (ldict['tg'])
The number and names of locals must be known in advance by the compiler in Python3 for performance optimizations. (This doesn't apply to globals()
, they still write through.)
If you know them in advance, you can just assign from them, e.g.
tg = ldict['tg']
print(tg)
If you need more than one you could unpack a dictionary into locals, like
a, b, c = (ldict[key] for key in ['a', 'b', 'c'])
Or you could dump the whole dict into a simple namespace and access them with .
instead of []
.
from types import SimpleNamespace
# ...
ns = SimpleNamespace(**ldict)
print(ns.tg)
You could also just exec
any code that needs the new locals, since you can give exec
the ldict
namespace.
exec("print(tg)", globals(), ldcit)
I understand that your example code may be simplified from the original, but it does not appear to need exec
at all. It is generally considered bad form to use exec
unless you absolutely need it, since it confuses static analysis tools and compiling strings at runtime is slow, especially if repeated in a loop like that.
If you must use exec, it's better to put the loop inside the exec string (use triple quotes) than the exec call inside the loop. That way the string only has to be compiled once, instead of for each loop.