list comprehension in exec with empty locals: NameError

前端 未结 5 1207
清歌不尽
清歌不尽 2020-12-15 22:55

Consider the following snippet:

def bar():
    return 1
print([bar() for _ in range(5)])

It gives an expected output [1, 1, 1, 1, 1]<

5条回答
  •  清歌不尽
    2020-12-15 23:22

    As Hendrik Makait found out, the exec documentation says that

    If exec gets two separate objects as globals and locals, the code will be executed as if it were embedded in a class definition.

    You can get the same behaviour by embedding the code into a class definition:

    class Foo:
        def bar():
            return 1
        print([bar() for _ in range(5)])
    

    Run it in Python 3 and you will get

    Traceback (most recent call last):
      File "foo.py", line 9, in 
        class Foo:
      File "foo.py", line 15, in Foo
        print({bar() for _ in range(5)})
      File "foo.py", line 15, in 
        print({bar() for _ in range(5)})
    NameError: global name 'bar' is not defined
    

    The reason for the error is as Hendrik said that a new implicit local scope is created for list comprehensions. However Python only ever looks names up in 2 scopes: global or local. Since neither the global nor the new local scope contains the name bar, you get the NameError.

    The code works in Python 2, because list comprehensions have a bug in Python 2 in that they do not create a new scope, and thus they leak variables into their current local scope:

    class Foo:
        [1 for a in range(5)]
        print(locals()['a'])
    

    Run it in Python 2 and the output is 4. The variable a is now within the locals in the class body, and retains the value from the last iteration. In Python 3 you will get a KeyError.

    You can get the same error in Python 2 too though, if you use a generator expression, or a dictionary/set comprehension:

    class Foo:
        def bar():
            return 1
        print({bar() for _ in range(5)})
    

    The error can be produced also by just using simply

    class Foo: 
        bar = 42
        class Bar:
            print(bar)
    

    This is unlike

    def foo():
        bar = 42
        def baz():
            print(bar)
        baz()
    

    because upon execution of foo, Python makes baz into a closure, which will access the bar variable via a special bytecode instruction.

提交回复
热议问题