AnalysisNerd
AnalysisNerd

Reputation: 133

Eval function in Dictionary working with individual references but not as dictionary comprehension

I have a list of variables and values that I assign to them using an eval statement.

I am trying to use a dictionary comprehension to match the variable with the evaluated value.

When I use a for loop for i in range(0,10) where len(ChosenVarNameList) = 10:

 dictinitial = {}
 for i in range (0,len(ChosenVarNameList)):
        dictinitial[ChosenVarNameList[i]] = eval("%s" %ChosenVarNameList[i])

I can create the dictionary.

When I reference individual indexes also I can see the dictionary populating correctly (with the code below).

      dictinitialnew = {ChosenVarNameList[0] : (eval("%s" 
      %ChosenVarNameList[0])).

However when I try a dictionary comprehension like the code below:

 dictinitialnew = {ChosenVarNameList[i] : (eval("%s" %ChosenVarNameList[i])) 
 for i in range (0,len(ChosenVarNameList)) }

I get code saying the first variable name let's say 'Code1' is not defined. Is there a way to do this using a dictionary comprehension or is there an alternative that I must use to get around this problem.

Thanks in advance.

Upvotes: 0

Views: 60

Answers (1)

ShadowRanger
ShadowRanger

Reputation: 155497

Your problem is due to dict comprehensions introducing nested scope. For most practical purposes, a dict comprehension in a function like:

def myfunc(iterable, y):
    return {x: y for x in iterable}

is implemented as something very similar to:

def myfunc(iterable, y):
    def _unnamed_func_(_unnamed_it_):
        retval = {}
        for x in _unnamed_it_:
            retval[x] = y  # Note: y is read from nested scope, not passed to inner func
        return retval
    return _unnamed_func_(iterable)  # Note: iterable passed as argument

That _unnamed_func_, like all functions with closure scope, determines what values from the nested scope are needed at the moment it is defined and folds them into its own closure scope; in this case, it needs y from the nested scope, but not iterable (because the first iterable you iterate over is passed to the virtual function as an argument, not through closure scope).

Problem is, eval is executed with knowledge of only the local and global scopes (as well as implicit knowledge of the builtin scope that all code has); it doesn't know about the nested scope, and since you only reference the variables via eval, the nested function doesn't know it needs them either.

You can demonstrate the general problem with simpler code:

def outer(x):
    def inner():
        return eval('x')
    return inner

If you try to run that with outer(1)() (and no x in global scope), it will die with NameError: name 'x' is not defined, because x was not part of the closure scope of inner, and it was promptly discarded when outer returned. Changing eval('x') to just x allows it to work (it returns 1 in the example case), because without eval getting in the way, inner pulls x into its closure scope at definition time so it's available when inner is run later.

Your design is a bad one to start with (eval should not be used for reading simple variable names), dict comprehensions just make it break completely.

The reason is behaves this way is that the language definitions for comprehensions are built off of the definition of a generator expression, and generator expressions must be implemented with closure scope; since they run lazily, if they didn't use a closure scope to keep nested variables they rely on alive, by the time they are run out the scope might no longer exist. list comprehensions in Python 2 used to execute without a closure, but it caused some weird artifacts (e.g. running foo = [x for x in y] in a class definition would give the class a class attribute named x with the final value x took in the comprehension) and in Python 3, all comprehensions were changed to use an implicit closure scope (this was only a change for listcomps; dict and set comprehensions were added later, and used closure scopes from the start).

Upvotes: 1

Related Questions