NameError using eval inside dictionary comprehensi

2019-05-18 15:44发布

问题:

I'm trying to code a dictionary inside my class:

data = {element:eval("self.%s" %element) for element in key}   

and I've got this error:

data = {element:eval("self.%s" %element) for element in key}
        File "<string>", line 1, in <module>
        NameError: name 'self' is not defined

If I do:

 for element in key:
     data[element]=eval("self.%s" %element)

No error here.

How come?

回答1:

TL:DR Summary -

As stated correctly by @CoryKramer , partially this is because dictionary comprehension/list comprehension/generator expressions/nested funcions all are evaluated in their own scope. But other half of the reason for this issue is because of the use of eval() , which executes its expressions in the environment in which it is called , but it does not have access to enclosing namespaces.

Alternatively ,I believe you should not use eval() (its pretty dangerous) . For getting attributes from self , you should use getattr() function -

data = {element:getattr(self,element) for element in key}

My findings about the issue for those who are interested -

Partially this is because dictionary comprehension/list comprehension/generator expressions/nested funcions all are evaluated in their own scope. But other half of the reason for this issue is because of the use of eval() .

As given in the documentation for eval() -

eval(expression[, globals[, locals]])

If both dictionaries are omitted, the expression is executed in the environment where eval() is called. The return value is the result of the evaluated expression. Syntax errors are reported as exceptions.

(Emphasis mine)

Normally inside a class , when you use dictionary comprehension you can use self and etc in that dictionary comprehension. Example -

>>> class CA:
...     def __init__(self):
...             self.a = "Hello"
...             print({k:self.a for k in range(2)})
...
>>> CA()
{0: 'Hello', 1: 'Hello'}
<__main__.CA object at 0x008B22D0>

As you can see it was possible to access self.a within the dictionary comprehension. So now lets check what is the locals() (local namespace) for the dictionary comprehension -

... #same as above, just change the print function call.
print({k:locals() if k < 2 else self.a for k in range(2)})

Result -

{0: {'.0': <range_iterator object at 0x02373998>, 'self': <__main__.CA object at 0x008B22D0>, 'k': 1}, 
1: {'.0': <range_iterator object at 0x02373998>, 'self': <__main__.CA object at 0x008B22D0>, 'k': 1}}

As can be seen 'self' is accessible inside the dictionary comprehension (as its a free variable , and this only occured because I used self.a directly inside the dictionary comprehension , if I did not add that it wouldn't be a free variable there. Lets explain free variables a bit -

If a name is bound in a block, it is a local variable of that block. If a name is bound at the module level, it is a global variable. (The variables of the module code block are local and global.) If a variable is used in a code block but not defined there, it is a free variable.

But when you use eval() to execute the expression, Python does not know about any names (beforehand, before executing the expression) that are used inside the eval() , hence it cannot bind the self as a free variable to the dicitonary comprehension. Example of print of locals() , when using eval to get self.a -

...
print({k:locals() if k < 2 else eval('self.a') for k in range(2)})

Result -

{0: {'.0': <range_iterator object at 0x023739B0>, 'k': 1}, 1: {'.0': <range_iterator object at 0x023739B0>, 'k': 1}}

Hence, when the expression is evaluated inside the eval it does not have the self variable defined in the environment it is executed. If you were to use self anywhere inside the dictionary comprehension , you would not end up with this error -

...
print({k:eval('self.a') if k < 2 else self for k in range(2)})

Result -

{0: 'Hello', 1: 'Hello'}

Because then the environment in which eval expression is getting executed has knows about the name binding self .

The exact same issue can be replicated using nested functions as well -

>>> def a():
...     localb = 10
...     def c():
...             print(locals())
...             print(eval('localb + 20'))
...     c()
...
>>> a()
{}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in a
  File "<stdin>", line 5, in c
  File "<string>", line 1, in <module>
NameError: name 'localb' is not defined


回答2:

Avoid using eval. A better practice is to use getattr:

data = {element: getattr(self, element) for element in key}