15

Consider the following snippet:

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

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

However, if I try to exec the same snippet in empty environment (locals and globals both are set to {}), it gives NameError:

if 'bar' in globals() or 'bar' in locals():
    del bar
# make sure we reset settings

exec("""
def bar():
    return 1
print([bar() for _ in range(5)])
""", {}, {})

NameError: name 'bar' is not defined

If I invoke exec like exec(…, {}) or exec(…), it is executed as expected.

Why?

EDIT:

Consider also the following snippet:

def foo():
    def bar():
        return 1
    print('bar' in globals()) # False
    print('bar' in locals()) # True
    print(['bar' in locals() for _ in [1]]) # [False]
    print([bar() for _ in [1, 2]]) # [1, 1]

Just like in my first exec, we don't have bar in locals inside list comprehension. However, if we try to invoke it, it works!

Ilya V. Schurov
  • 7,687
  • 2
  • 40
  • 78
  • Interestingly, this weird behavior is not seen in Python 2. – jmd_dk Jul 16 '17 at 19:47
  • 2
    Note that if you make locals & globals the same empty dict, it works: `d={}; exec("def bar():return 1\nprint([bar() for _ in range(5)])", d, d)` – PM 2Ring Jul 16 '17 at 20:08
  • LLia V Schurov, consider not give two dictionaries as parameter, in this case, the two extra parameters are not needed – developer_hatch Jul 16 '17 at 20:36
  • It was really hard to understand the rare behavior behind this, but I think I fanally with help of @PM2Ring I understand whats going on – developer_hatch Jul 16 '17 at 20:38
  • @DamianLattenero Oh, good. :) FWIW, another way to make it behave as desired is to manually update the globals: `exec("def bar():return 1\nglobals().update(locals());print([bar() for _ in range(5)])", {}, {})`. But of course one shouldn't do things like that in sane code... OTOH, one probably shouldn't being using `exec` either. ;) – PM 2Ring Jul 16 '17 at 20:42
  • 2
    For some excellent info about `exec`, `eval`, and `compile`, please see https://stackoverflow.com/a/29456463/4014959 – PM 2Ring Jul 16 '17 at 20:44
  • @PM2Ring You are incredible, do you let me qouting you in my answer and add this data? it's soooo good, I didn't know it could be done! – developer_hatch Jul 16 '17 at 20:45
  • @PM2Ring done :) thanks so much – developer_hatch Jul 16 '17 at 20:49
  • I updated my answer in order to answer your updated question. Let me know, is at the end – developer_hatch Jul 16 '17 at 21:29
  • @PM2Ring I ran into a similar problem when developing a game. I needed a script engine, and I didn't want to develop a new one when the python engine itself if available. All scripts I'm going to run are static, so it should be safe. – Imperishable Night Nov 16 '18 at 00:02
  • @PM2Ring Although to be honest, the most important feature I was looking for is to populate a namespace with a `kwargs` dictionary and just use them as local variables. Maybe I can make the scripts into python functions, and explicitly declare the variables I want as named parameters, but it still feels like a hassle: when most of the functions are one-liners, even a function definition feels like boilerplates. – Imperishable Night Nov 16 '18 at 00:17
  • @PM2Ring Anyway, in my case, `exec(code, {**globals(), kwargs})` works well enough. From my understanding, if only one dictionary is given, it's used as both globals and locals, so it should work like your first example. Not that my scripts usually change the locals dictionary: it's hard to do so with one-liners :) – Imperishable Night Nov 16 '18 at 00:20

5 Answers5

14

The solution to your problem lies here:

In all cases, if the optional parts are omitted, the code is executed in the current scope. If only globals is provided, it must be a dictionary, which will be used for both the global and the local variables. If globals and locals are given, they are used for the global and local variables, respectively. If provided, locals can be any mapping object. Remember that at module level, globals and locals are the same dictionary. If exec gets two separate objects as globals and locals, the code will be executed as if it were embedded in a class definition.

https://docs.python.org/3/library/functions.html#exec

Basically, your problem is that bar is defined in the scope of locals and only in locals. Therefore, this exec() statement works:

exec("""
def bar():
    return 1
print(bar())
""", {}, {})

The list comprehension however creates a new local scope, one in which bar is not defined and can therefore not be looked up.

This behaviour can be illustrated with:

exec("""
def bar():
    return 1
print(bar())
print(locals())
print([locals() for _ in range(1)])
""", {}, {})

which returns

1
{'bar': <function bar at 0x108efde18>}
[{'_': 0, '.0': <range_iterator object at 0x108fa8780>}]

EDIT

In your original example, the definition of bar is found in the (module level) global scope. This corresponds to

Remember that at module level, globals and locals are the same dictionary.

In the exec example, you introduce an artificial split in scopes between globals and locals by passing two different dictionaries. If you passed the same one or only the globals one (which would in turn mean that this one will be used for both globals and locals) , your example would also work.

As for the example introduced in the edit, this boils down to the scoping rules in python. For a detailed explanation, please read: https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces

In short, while bar is not in the local scope of the list comprehension and neither in the global scope, it is in the scope of foo. And given Python scoping rules, if a variable is not found in the local scope, it will be searched for in the enclosing scopes until the global scope is reached. In your example, foo's scope sits between the local scope and the global scope, so bar will be found before reaching the end of the search.

This is however still different to the exec example, where the locals scope you pass in is not enclosing the scope of the list comprehension, but completely divided from it.

Another great explanation of scoping rules including illustrations can be found here: http://sebastianraschka.com/Articles/2014_python_scope_and_namespaces.html

Hendrik Makait
  • 1,042
  • 5
  • 14
  • thanks, I expected something like that. However, I'm still not sure, why it works inside function (see edit to my question). – Ilya V. Schurov Jul 16 '17 at 21:00
  • @IlyaV.Schurov - see edit to my answer. TL;DR: in exec you are creating an artificial scoping that does not reflect the "pure code" example's scope. – Hendrik Makait Jul 16 '17 at 21:02
  • @IlyaV.Schurov also see my edited answer please, and comment there whatever you don't understand – developer_hatch Jul 16 '17 at 21:03
  • @HendrikMakait we don't have 'bar' in globals() inside the function, as the first print shows. Does it mean we don't have it in global scope as well? – Ilya V. Schurov Jul 16 '17 at 21:07
  • @IlyaV.Schurov - indeed it does. I highly recommend reading one of the links I shared to get a deeper understanding. The scoping rules are pretty powerful once understood! – Hendrik Makait Jul 16 '17 at 21:17
  • 1
    @HendrikMakait it seems I understand now. In exec example, Python can't find foo in the list comprehension scope and it tries to find it in the outer scope, which appear to be the topmost one. As it is the topmost scope, Python looks in globals dictionary. Normally topmost's locals is the same as globals and objects defined in topmost scope are recorded to both topmost's locals and globals. But in exec example it is recorded only in locals, which is not scanned by Python. Am I right? – Ilya V. Schurov Jul 16 '17 at 22:00
  • Yes, exactly! The case with foo is a bit more complicated than that, but you seem to be on a great way of understanding it. – Hendrik Makait Jul 16 '17 at 22:06
  • Thanks a lot! I used to believe I understand Python scopes but this thing with splitting of locals and globals on the topmost level is a really strange :) – Ilya V. Schurov Jul 16 '17 at 22:09
  • Just to be sure, I tried to enclose my last snippet into exec with two empty dicts, and it works, because in this case bar is defined in a normal locals of foo, not in the strangle splitted topmost locals. It's now in agreement with my understanding :) – Ilya V. Schurov Jul 16 '17 at 22:13
  • @IlyaV.Schurov - You're welcome. It is definitely counterintuitive. It took me a while to understand what "If exec gets two separate objects as globals and locals, the code will be executed as if it were embedded in a class definition." meant. – Hendrik Makait Jul 16 '17 at 22:15
  • @HendrikMakait , I read carefully your answer, it is a great one, after all the edits, I will return my upvote because I learnt a lot with all the references and explanation you have provided, Thanks so much for the time! – developer_hatch Jul 17 '17 at 02:13
  • This contains no solution, and misses the actual explanation: exec() doesn't look up in the enclosing namespace only the local, which is empty, and the global. – Lennart Regebro Feb 10 '18 at 10:09
5

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 <module>
    class Foo:
  File "foo.py", line 15, in Foo
    print({bar() for _ in range(5)})
  File "foo.py", line 15, in <setcomp>
    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.

4

I'm late to the party here, but there is a better documentation reference buried in the execution model.

In section 4.2.2 Resolution of names:

Class definition blocks and arguments to exec() and eval() are special in the context of name resolution. ...

And then in 4.2.4 Interaction with dynamic features:

The eval() and exec() functions do not have access to the full environment for resolving names. Names may be resolved in the local and global namespaces of the caller. Free variables are not resolved in the nearest enclosing namespace, but in the global namespace. [1] The exec() and eval() functions have optional arguments to override the global and local namespace. If only one namespace is specified, it is used for both.

[1] This limitation occurs because the code that is executed by these operations is not available at the time the module is compiled.

wim
  • 338,267
  • 99
  • 616
  • 750
2

Here's a solution!

We needed to get the local namespace out after the exec() to track modifications. This doesn't work with only one namespace, so we did this:

 class MagickNameSpace(UserDict, dict):
    """A magic namespace for Python 3 exec().
    We need separate global and local namespaces in exec(). This does not
    work well in Python 3, because in Python 3 the enclosing namespaces are
    not used to look up variables, which seems to be an optimization thing
    as the exec'd code isn't available at module compilation.

    So we make a MagickNameSpace that stores all new variables in a
    separate dict, conforming to the local/enclosing namespace, but
    looks up variables in both.
    """


    def __init__(self, ns, *args, **kw):
        UserDict.__init__(self, *args, **kw)
        self.globals = ns

    def __getitem__(self, key):
        try:
            return self.data[key]
        except KeyError:
            return self.globals[key]

    def __contains__(self, key):
        return key in self.data or key in self.globals

Replace the old code:

exec(code, global_ns, local_ns)
return local_ns

with:

ns = MagickNameSpace(global_ns)
ns.update(local_ns)
exec(code, ns)
return ns.data
Lennart Regebro
  • 167,292
  • 41
  • 224
  • 251
1

Edit

To answer your edited question, user @Hendrik Makait said bar is not in the scope of list comprehension:

def foo():
    def bar():
        return 1
    print('bar' in globals()) # False, because the scope of foo and bar are diferents, foo is globals() scope, bar are in the scope of foo
    print('bar' in locals()) # True
    print(['bar' in locals() for _ in [1]]) # [False], because a new implicit scope is defined in list comprehension, as user @Antti Haapala said
    print([bar() for _ in [1, 2]]) # [1, 1]

To answer the original question:

If you create two different dictionaries, it wont recognize the local and globals definitions, the variables are not updated as @PM 2Ring said:

exec("""
def bar():
    return 1
print(bar())
print("bar" in globals())
print("bar" in locals())
print([bar() for _ in range(5)])
""", {},{})

it prints:

1
False #not in globals
True
Traceback (most recent call last):
  File "python", line 17, in <module>
  File "<string>", line 7, in <module>
  File "<string>", line 7, in <listcomp>
NameError: name 'bar' is not defined

A way to do it, is update the variables, like this globals().update(locals()):

exec("""
def bar():
    return 1
globals().update(locals())
print("bar" in globals())
print("bar" in locals())
print([bar() for _ in range(5)])
""", {}, {})

wich gives:

True
True
[1, 1, 1, 1, 1]

But, if you remove the dictionaries, or create one and give it to the exec function as same parameter, it works:

d={}

exec("""
def bar():
    return 1
print("bar" in globals())
print("bar" in locals())
print([bar() for _ in range(5)])
""",d,d)

it prints:

True
True
[1, 1, 1, 1, 1]

That's why you get the error, it could't find your function in the globals

Or simply, don't give the parameters:

exec("""
def bar():
    return 1
print(bar())
print("bar" in globals())
print("bar" in locals())
print([bar() for _ in range(5)])
""")

Cause the same efect.

developer_hatch
  • 15,898
  • 3
  • 42
  • 75