1

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

zxdawn
  • 825
  • 1
  • 9
  • 19

3 Answers3

1

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.

gilch
  • 10,813
  • 1
  • 23
  • 28
1
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('{} = {}'.format(key, val))
    print (tg)

    local = locals()
    for key in measurements.keys():
        print 'Key: ', key, ', Value: ', local[key]
f()

python3:

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('global {};{} = {}'.format(key, key, val))
    print ('tg: ', tg)

    vars = globals()
    for key in measurements.keys():
        print ('Key: ', key, ', Value: ', vars[key])
f()

output:

[8.184e+16, 8.345e+16, 8.045e+16, 8.52e+16, 8.322e+16, 7.622e+16, 4.305e+16, 2.203e+16]
Key:  tg , Value:  [8.184e+16, 8.345e+16, 8.045e+16, 8.52e+16, 8.322e+16, 7.622e+16, 4.305e+16, 2.203e+16]
user3713719
  • 325
  • 3
  • 8
  • `exec('{} = {}'.format(key, val))` doesn't work for Python 3. `NameError: name 'tg' is not defined` – zxdawn Oct 11 '18 at 05:47
  • Thanks. If I'm using `measurements = {'tg': np.array([8.184e+16, 8.345e+16, 8.045e+16, 8.520e+16, 8.322e+16, 7.622e+16, 4.305e+16, 2.203e+16])}` instead of list. I'll get this error:` global tg;tg = [8.184e+16 8.345e+16 8.045e+16 8.520e+16 8.322e+16 7.622e+16 4.305e+16 ^ SyntaxError: invalid syntax` – zxdawn Oct 11 '18 at 06:40
  • use np.array() inside exec `>>> 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]} >>> for key,val in measurements.items(): ... exec('global {};{} = np.array({})'.format(key, key, val)) ... >>> tg array([8.184e+16, 8.345e+16, 8.045e+16, 8.520e+16, 8.322e+16, 7.622e+16, 4.305e+16, 2.203e+16])` – user3713719 Oct 11 '18 at 19:09
0

Years later, but I've since realized that you can (ab)use the temporary namespace made in class declarations to get a write-through locals() namespace inside of functions. This allows you to translate the Python 2 version pretty directly:

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():
    class _:
        for key,val in measurements.items():
            exec(key + ' = val', globals(), locals())
        print (tg)

f()
gilch
  • 10,813
  • 1
  • 23
  • 28