J.Warren
J.Warren

Reputation: 778

Scope within eval in list comprehension with method

I am confused about methods combined with list comprehension and the eval statement. The code below errors on the test7 line with the error NameError: name 'a2' is not defined.

class test_class(object):
    def __init__(self):
        pass
    @property
    def property(self):
        return 'test_method_run'

def run():
    a2 = test_class()
    test3 = eval('a.property')
    test4 = [eval('a.property') for i in range(10)]
    test5 = eval('a2.property')
    test6 = [a2.property for i in range(10)]
    test7 = [eval('a2.property') for i in range(10)

a = test_class()
test1 = eval('a.property')
test2 = [eval('a.property') for i in range(10)]
run()

It must have something to do with scope (eval fails in list comprehension). My understanding of scope in python 2 (I have just moved to python 3) was that a should not be defined within run(), but a2 is. I'm further confused by the impact of the list comprehension. My expectation was that test2 and test3 lines should fail as a is not defined with the test method. Also I expected that if test5 runs OK then test6 and test7 should also be fine.

This error only occurs when eval is used in a list comprehension within a function... If any of these 3 elements are not present then there is no error. My question is why? I don't feel like I understand it enough to form a better question.

Upvotes: 2

Views: 307

Answers (1)

Kevin
Kevin

Reputation: 76234

My understanding of scope in python 2 (I have just moved to python 3) was that a should not be defined within run(), but a2 is.

Both a and a2 are visible from within run. a is defined at the global scope, so it is visible everywhere in that file.

I expected that if test5 runs OK then test6 and test7 should also be fine.

In 3.X, list comprehensions get their own scope. The test6 list comprehension has access to three scopes: the scope of the list comprehension, the scope of the function, and the global scope. So it has access to i and a2 and a.

By default, code executed inside eval has access to two scopes: the global scope and the closest local scope. This means the test7 eval can access variables defined at the file level and it can access variables defined inside the list comprehension, but it can't access variables defined inside the function but outside of the list comprehension. It can see a and i but not a2.

In 2.7, list comprehensions do not get their own scope. They share the same scope as the function that they're defined in. This explains why your code executes in 2.7 but not in 3.X. IIRC, this is the only change to the scope system between 2.7 and 3.X. (and if it isn't, it's the only change that's relevant to this scenario.)

Upvotes: 3

Related Questions