4

I'm trying to declare two functions within exec statement in python. Let's call them f1() and f2().

I've found out that when exec is invoked inside of some function, then f2() has no visibility of f1(). However this doesn't happen when exec and function call are placed in global code.

# Case 1: Working fine code = """ def f1(): print "bar" def f2(): f1() """ exec(code) f2() # Prints "bar" as expected 
# Case 2: Throws NameError: global name 'f1' is not defined code = """ def f1(): print "bar" def f2(): f1() """ def foo(): exec(code) f2() # NameError foo() 

Can someone explain me how to avoid that NameError and make exec work inside of a function?

4
  • 3
    Pardon my being flippant, but the best way to avoid problems with exec is to never use exec. There is almost always a better approach. Commented Apr 19, 2019 at 14:13
  • 1
    I know that Python's bytecode compiler has to make certain decisions about the visibility of names at parse time. e.g. whether to emit the instruction LOAD_GLOBAL, or LOAD_DEREF, or LOAD_FAST, etc. exec effectively creates its own independent parsing context, so the bytecode compiler can't make a fully informed decision about name resolution, unlike if you had defined each function normally. (Posting this as a comment and not an answer because it's too handwavey to actually give you an idea of what specifically is happening and how to fix it) Commented Apr 19, 2019 at 14:45
  • Related: Why does my function that calls exec on marshalled code give an error about globals? Commented Apr 28, 2019 at 17:53
  • You can refer to my question and my answer. In case one, the f1() is added to globals, but in case2, f1() is local. So f1 should be added to f2.__closure__ in case2. However, in case2 the definition of f2() can not make a correct closures. My quesition and my answer explain why this happen and how to deal with it. Compared with existing answers, my solution avoid making f1() global in case2. Commented May 26, 2022 at 5:57

3 Answers 3

5

exec() accepts a second parameter for globals. As explained in the docs:

Note The built-in functions globals() and locals() return the current global and local dictionary, respectively, which may be useful to pass around for use as the second and third argument to exec().

So you can make this work by explicitly passing in globals():

code = """ def f1(): print ("bar") def f2(): f1() """ def foo(): exec(code, globals()) f2() # works in python2.7 and python3 foo() 

If you want to control the scope precisely, you can pass an object into exec:

code = """ def f1(): print ("bar") def f2(): f1() """ def foo(): context = {} exec(code, context) context['f2']() foo() 
Sign up to request clarification or add additional context in comments.

1 Comment

It's important to note that the reason this works is not because it's more explicit, it's because this solution makes the global namespace for the execed code and the local namespace for the execed code the same so that whenever a local function is defined (like f1) it gets copied as a global.
2

Kevin's comment is a good one and bears some repeating - using exec is dangerous there is almost always a better approach.

However, in response to your question in the first case, both f1() and f2() are in the global namespace so when you call f2() it can find f1(). In the second case, they are created in the local space of the foo() function. When f2() is called it can't find the local f1() definition.

You can fix this using:

code = """ global f1 def f1(): print "bar" def f2(): f1() """ def foo(): exec(code) f2() foo() 

Again - this is almost certainly not the way you want to solve this problem.

** EDIT ** Posted wrong version of the code which I had been checking, this version is what I had meant to include.

6 Comments

f1 and f2 are both defined within the same scope (foo) why won't f2 be able to find f1? Ans your code doesn't make sense, since there is no global f1 defined when the exec happens
"When f2() is called it can't find the local f1() definition." But why not? Ordinarily, a function defined in a local scope can access any variable defined in that same scope. I agree there is a lookup problem, but it seems to be caused by something other than the usual name resolution rules.
global f1 indeed makes code work. But I still don't get it. If both f1 and f2 were defined locally, how come they are not able to see each other?
f1 doesn't have to be defined when global f1 is called. The statement just indicates the name f1 should be in the global scope. This gets complicated ( part of why it is a bad idea ). I'm not 100% sure here but normally the value f1 gets captured throught the closure link. When you use exec it doesn't work. See stackoverflow.com/questions/2749655/… for another example of this.
It think the problem is that at the time f2 is compiled, f1 doesn't actually exist in the current scope yet: the function does, it just hasn't been bound to a name in foo's scope, which means f2 gets compiled with f1 as a global name.
|
-1

"In all cases, if the optional parts [of exec()] are omitted, the code is executed in the current scope."

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

5 Comments

OP is using python 2, where exec is a statement. The behaviour of exec in python 2.7 and python 3.x are very different. In python3, the second code posted by OP would give a NameError for f2 itself
I don't think scoping entirely explains the outcome here. If you defined f1 and f2 normally within foo's body, then f2 would have no problem calling f1. So the problem can't be "functions defined in a non-global scope can't see one another"; this is empirically not the case.
I realize that op included the python 2.7 tag, but the use of exec as a function in the code example provided would indicate he is using python 3.
Maybe, maybe not. exec(code) is also valid syntax in Python 2.7, for the same reason that print("Hello, world!") is valid syntax: superflous parentheses around an expression are a no-op.
Looking at OP's 'print "bar"', I am wrong about it being python 3, but looks like I was right about passing in the globals, as @MarkMeyer 's answer dose resolve OP's issue.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.