scope of eval function in python

2019-02-07 23:17发布

问题:

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?

回答1:

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}


回答2:

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}