Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using exec() with recursive functions

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

like image 494
Headcrab Avatar asked May 16 '09 07:05

Headcrab


People also ask

What is recursive execution?

So, what is recursion? A recursive function is a function that calls itself until a “base condition” is true, and execution stops.

How do you call a recursive function in Javascript?

The syntax for recursive function is: function recurse() { // function code recurse(); // function code } recurse(); Here, the recurse() function is a recursive function. It is calling itself inside the function.

How recursive functions are executed in C?

In C programming, a function is allowed to call itself. A function which calls itself directly or indirectly again and again until some specified condition is satisfied is known as Recursive Function. A recursive function is a function defined in terms of itself via self-calling expressions.

What are recursive functions give three examples?

Simple examples of a recursive function include the factorial, where an integer is multiplied by itself while being incrementally lowered. Many other self-referencing functions in a loop could be called recursive functions, for example, where n = n + 1 given an operating range.


3 Answers

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

like image 117
Brian Avatar answered Oct 22 '22 14:10

Brian


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()
like image 41
Unknown Avatar answered Oct 22 '22 14:10

Unknown


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)
like image 3
stephan Avatar answered Oct 22 '22 12:10

stephan