Myam
Myam

Reputation: 43

Why exec() works differently when invoked inside of function and how to avoid it

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?

Upvotes: 4

Views: 133

Answers (3)

Mark
Mark

Reputation: 92440

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() 

Upvotes: 5

tarkmeper
tarkmeper

Reputation: 767

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.

Upvotes: 2

JDunken
JDunken

Reputation: 463

"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

Upvotes: -1

Related Questions