20

Consider the following example:

i=7
j=8
k=10
def test():
    i=1
    j=2
    k=3
    return dict((name,eval(name)) for name in ['i','j','k'])

It returns:

>>> test()
{'i': 7, 'k': 10, 'j': 8}

Why eval does not take into consideration the variables defined inside the function? From the documentation, optionally you can pass a globals and a locals dictionary. What does it means?Finally, how can I modify this small case to make it work?

3
  • 3
    you could modify it to work by adding global before variable declaration inside a function but that's a bad idea, on the other hand, using eval is usually a bad idea as well. Commented Dec 9, 2014 at 12:17
  • What rusty said - unless you're sure you have to use eval, stay far away from it. Commented Dec 9, 2014 at 12:55
  • @l4mpi I knew that eval is a bad idea, but i was just playing around with scopes, and i just didn't understand this behaviour Commented Dec 9, 2014 at 13:10

2 Answers 2

13

Generators are implemented as function scopes:

The scope of names defined in a class block is limited to the class block; it does not extend to the code blocks of methods – this includes generator expressions since they are implemented using a function scope.

So, the generator inside the dict() constructor has its own locals() dictionary. Now let's take a look at Py_eval's source code, specially when both globals() and locals() are None:

if (globals == Py_None) {
        globals = PyEval_GetGlobals();
        if (locals == Py_None)
            locals = PyEval_GetLocals();
    }

So, for your example PyEval_GetLocals() will be empty at the moment the loop is executing and globals() will be the global dictionary. Note that i, j and k defined inside the function are not in local scope of generator, rather they are in its enclosing scope:

>>> dict((name,eval(name, globals(), {})) for name in ['i', 'j', 'k'])
{'i': 7, 'k': 10, 'j': 8}
Sign up to request clarification or add additional context in comments.

Comments

4

This occurs because the generator expression has a different scope to the function:

>>> def test():
    i, j, k = range(1, 4)
    return dict((j, locals()) for _ in range(i))

>>> test()
{2: {'.0': <listiterator object at 0x02F50A10>, 'j': 2, '_': 0}}

Using j inside the scope binds it from the function, as that's the nearest enclosing scope, but i and k are not locally bound (as k isn't referenced and i is only used to create the range).


Note that you can avoid this issue with:

return dict(i=i, j=j, k=k)

or a dictionary literal:

return {'i': i, 'j': j, 'k': k}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.