Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Python's 'exec' behaving oddly?

Why does the following code work while the code after it breaks?

I'm not sure how to articulate my question in english, so I attached the smallest code I could come up with to highlight my problem.

(Context: I'm trying to create a terminal environment for python, but for some reason the namespaces seem to be messed up, and the below code seems to be the essence of my problem)

No errors:

d={}
exec('def a():b',d)
exec('b=None',d)
exec('a()',d)

Errors:

d={}
exec('def a():b',d)
d=d.copy()
exec('b=None',d)
d=d.copy()
exec('a()',d)
like image 637
Ryan Burgert Avatar asked Dec 20 '25 22:12

Ryan Burgert


1 Answers

It is because the d does not use the globals provided by exec; it uses the mapping to which it stored the reference in the first exec. While you set 'b' in the new dictionary, you never set b in the globals of that function.

>>> d={}
>>> exec('def a():b',d)
>>> exec('b=None',d)
>>> d['a'].__globals__ is d
True
>>> 'b' in d['a'].__globals__
True

vs

>>> d={}
>>> exec('def a():b',d)
>>> d = d.copy()
>>> exec('b=None',d)
>>> d['a'].__globals__ is d
False
>>> 'b' in d['a'].__globals__
False

If exec didn't work this way, then this too would fail:

mod.py

b = None
def d():
    b

main.py

from mod import d
d()

A function will remember the environment where it was first created.


It is not possible to change the dictionary that an existing function points to. You can either modify its globals explicitly, or you can make another function object altogether:

from types import FunctionType

def rebind_globals(func, new_globals):
    f = FunctionType(
        code=func.__code__,
        globals=new_globals,
        name=func.__name__,
        argdefs=func.__defaults__,
        closure=func.__closure__
    )
    f.__kwdefaults__ = func.__kwdefaults__
    return f


def foo(a, b=1, *, c=2):
    print(a, b, c, d)


# add __builtins__ so that `print` is found...    
new_globals = {'d': 3, '__builtins__': __builtins__}
new_foo = rebind_globals(foo, new_globals)
new_foo(a=0)
like image 130


Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!