Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

eval fails in list comprehension [duplicate]

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.

like image 519
cbsteh Avatar asked Jul 19 '17 15:07

cbsteh


1 Answers

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
like image 163
Willem Van Onsem Avatar answered Sep 28 '22 06:09

Willem Van Onsem