views:

322

answers:

4

I want to execute some Python code, typed at runtime, so I get the string and call

exec(pp, globals(), locals())

where pp is the string. It works fine, except for recursive calls, e. g., for example, this code is OK:

def horse():
    robot.step()
    robot.step()
    robot.turn(-1)
    robot.step()

while True:
    horse()

But this one is not:

def horse():
    robot.step()
    robot.step()
    robot.turn(-1)
    robot.step()
    horse()

horse()

NameError: global name 'horse' is not defined

Is there a way to run recursive code as well?

UPDATE

a = """\
def rec(n):
    if n > 10:
        return
    print n
    return rec(n+1)

rec(5)"""

exec(a)

Works if put on the top-level. But if moved inside a function:

def fn1():
    a = """\
def rec(n):
    if n > 10:
        return
    print n
    return rec(n+1)

rec(5)"""

    exec(a)

fn1()

the same error occurs: NameError: global name 'rec' is not defined

+4  A: 

It works for me:

a = """\
def rec(n):
    if n > 10:
        return
    print n
    return rec(n+1)

rec(5)"""

exec(a)
5
6
7
8
9
10

All I can say is that there is probably a bug in your code.

Edit

Here you go

def fn1():
    glob = {}
    a = """\
def rec(n):
    if n > 10:
        return
    print n
    return rec(n+1)

rec(5)"""
    exec(a, glob)

fn1()
Unknown
Well, not exactly - see the UPDATE above...
Headcrab
Why does it work?
Headcrab
@Headcrab, because it gives you an editable globals and locals dictionary. locals() gives you a dictionary that can't change the "real" local variables.
Unknown
You mean, if the "globals" is not present, exec uses some default read-only one instead, and "rec" can't be added there (because it's read-only), and that's why the error occurs?
Headcrab
@Headcrab, I believe that is correct. Exec assumes that calls to variables are globals, since it can't see that its wrapped in a function. What really baffles me is why exec(a, globals()) seems to work which indicates a locals() problem.
Unknown
It's actually nothing to do with locals being read-only - it will happen any time you use a seperate dictionary for locals and globals. exec s in {}, {} will have the same problem. exec(a, globals()) works because it's the same as exec(a, globals(), globals()) so local definitions are also put into globals.
Brian
A: 

"NameError: global name 'rec' is not defined" means it's looking for rec in the global scope, not the local scope. Looks like it's defining rec in the local scope but then attempting to execute in the global. Try printing locals() and globals() in side the string you're executing.

More info.

apphacker
+2  A: 

This works for me (added global rec). rec(5) calls the local rec, but rec(n+1) calls a global rec (which doesn't exist) without it.

def fn1():
    a = """global rec
def rec(n):
    if n > 10:
        return
    print n
    return rec(n+1)

rec(5)"""

    exec(a)
stephan
+1  A: 

This surprised me too at first, and seems to be an odd corner case where exec is acting neither quite like a top-level definition, or a definition within an enclosing function. It looks like what is happening is that the function definition is being executed in the locals() dict you pass in. However, the defined function does not actually have access to this locals dict.

Normally, if you define a function at the toplevel, locals and globals are the same, so functions are visible within because they can see the function in the globals.

When a function is defined within another function's scope, python will notice that it is accessed within the function, and create a closure so that "horse" maps to the binding in the outer scope.

Here, it's a weird halfway case. exec is acting as if the definitions are at top-level, so no closures are created. However, since locals is not the same as globals, the definition doesn't go where the function can access it - its defined only in the inaccessible outer locals dict.

There are a couple of things you could do:

  1. Use the same dictionary for both locals and globals. ie "exec s in locals(),locals()" (or better, just use your own dict). Providing only a globals() dict has the same effect - ie "exec s in mydict" #
  2. Put the func inside its own function, so that a closure is created. eg

    s="""
    def go():
        def factorial(x):
            if x==0: return 1
            return x*factorial(x-1)
        print factorial(10)
    go()"""
    
  3. Force the function to go into globals() rather than locals by putting a "global funcname" directive, as suggested by stephan's answer

Brian