3

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)
asked Dec 14, 2017 at 8:07
2
  • 1
    That's your hint to not use exec and eval ;-) Commented Dec 14, 2017 at 8:18
  • Tricky. Note that if you do the exec that defines b before the one that defines a then the 2nd version works. Or if you get rid of the 1st copy (but I guess you already know that). I'm still not quite sure why the copying messes stuff up, but I guess it's has something to do with the fact that b is a global Commented Dec 14, 2017 at 8:37

1 Answer 1

2

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)
answered Dec 14, 2017 at 10:13
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you - this does help! Is there any way to go about injecting globals into a function? That way I could change the value of b in main.py in a way that b() would respond to
@RyanBurgert added

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.