GooT
GooT

Reputation: 70

Python:dict comprehension and eval function variable scope

Code 1: for loop

def foo():
    one = '1'
    two = '2'
    three = '3'
    d = {}
    for name in ('one', 'two', 'three'):
        d[name] = eval(name)
    print(d)

foo()

output:

{'one': '1', 'two': '2', 'three': '3'}

Code 2: dict comprehension

def foo():
    one = '1'
    two = '2'
    three = '3'
    print({name: eval(name) for name in ('one', 'two', 'three')})

foo()

output:

NameError: name 'one' is not defined

Code 3: add global keyword

def foo():
    global one, two, three  # why?
    one = '1'
    two = '2'
    three = '3'
    print({name: eval(name) for name in ('one', 'two', 'three')})

foo()

output:

{'one': '1', 'two': '2', 'three': '3'}

Dict comprehensions and generator comprehensions create their own local scope. According to the definition of the closure (or not the closure here), but why can't Code 2 access the variable one[,two,three] of the outer function foo? However, Code 3 can successfully create a dictionary by setting the variable one[,two,three] to global?

So is it because the eval function and the dict comprehensions have different scopes?

Hope someone help me, I will be grateful!

Upvotes: 0

Views: 369

Answers (1)

jferard
jferard

Reputation: 8180

To understand whats happening, try this:

def foo():
    global one
    one = '1'
    two = '2'
    print({'locals, global': (locals(), globals()) for _ in range(1)})

foo()

Output

{'locals, global': ({'_': 0, '.0': <range_iterator object at ...>},
                    {'__name__': '__main__', '__package__': None, ..., 'one': '1'})}

The builtin eval(expression) is a shortcut for eval(expression[, globals[, locals]]).

As you see in the previous output, locals() is not local symbol table of the function because list/dict comprehensions have their own scope (see https://bugs.python.org/msg348274 for instance).

To get the output you expected, you just have to pass the local symbol table of the function to eval.

def bar():
    one = '1'
    two = '2'
    three = '3'
    func_locals = locals() # bind the locals() here
    print({name: eval(name, globals(), func_locals) for name in ('one', 'two', 'three')})

bar()

Output

{'one': '1', 'two': '2', 'three': '3'}

Upvotes: 1

Related Questions