This question already has an answer here:
-
Python: How can I run eval() in the local scope of a function
2 answers
Consider the following hypothetical code:
class B(object):
def __init__(self):
self.b = 2
def foo(self):
out1 = [eval('self.b')] # ok
print(out1) # prints: [2]
out2 = [eval(cmd) for cmd in ['self.b']] # fails
print(out2) # NameError: name 'self' is not defined
b = B()
b.foo()
Why is the statement for out1
ok, but not for out2
, which gives the error "'self' is not defined"?
I am learning Python, and I came about this problem whilst experimenting about eval
. Yes, I know the use of eval
in this example is inappropriate, but just for the sake of taking this example at face value, can someone explain why the statement for out2
gives out the error message? It seems both statements should work and give the same result.
Thank you for any guidance.
By using list comprehension, you actually define a new scope. Indeed if we alter the list comprehension to:
out2 = [print(globals()) or print(locals()) or eval(cmd) for cmd in ['self.b']]
we force Python to print the local and global variables before making the eval(..)
call, and we obtain something like:
{'__builtins__': <module 'builtins' (built-in)>, '__name__': '__main__', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, 'b': <__main__.B object at 0x7f406f55d4a8>, '__doc__': None, '__package__': None, 'B': <class '__main__.B'>, '__spec__': None}
{'.0': <list_iterator object at 0x7f406f55df28>, 'cmd': 'self.b'}
So as local variables we only have a .0
and a cmd
.
You can however pass self
to the list comprehension by using:
globs = globals()
locs = locals()
out2 = [eval(cmd,globs,locs) for cmd in ['self.b']]
So now eval(..)
will use the local and global variables as defined in the scope of the function. Since we use locs
and globs
. Python will pass references to these dictionaries to the eval(..)
call.
Finally a warning as with every use of eval(..)
: eval is a dangerous function. You better use it only if you really need it.
An additional side effect of this additional scope (introduced in python-3.x) is that the loop variable does not leak: after the list comprehension cmd
is cleaned up: you can no longer access it (usually it would hold the last element it has handled). For example:
>>> [x for x in range(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined