在递归函数中使用 exec()

9 投票
4 回答
2496 浏览
提问于 2025-04-15 11:38

我想在运行时执行一些Python代码,所以我获取一个字符串,然后调用

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

这里的pp就是那个字符串。这样做是没问题的,除了在递归调用的时候,比如说,下面这段代码是可以正常工作的:

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

while True:
    horse()

但是这段就不行:

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

horse()

NameError: global name 'horse' is not defined

有没有办法让递归代码也能运行呢?

更新

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

rec(5)"""

exec(a)

如果把代码放在最外层是可以的。但如果把它放到一个函数里面:

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

rec(5)"""

    exec(a)

fn1()

同样会出现这个错误:NameError: global name 'rec' is not defined

4 个回答

3

对我来说,这个方法有效(我加了 global rec)。当我用 rec(5) 时,它会调用本地的 rec 函数,但如果我用 rec(n+1),它就会尝试调用一个全局的 rec 函数(这个函数并不存在)。

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

rec(5)"""

    exec(a)
6

这让我一开始也很惊讶,这似乎是一个奇怪的边缘情况,exec的表现既不像顶层定义,也不像封闭函数中的定义。看起来发生的事情是,函数定义是在你传入的locals()字典中执行的。然而,定义的函数实际上并不能访问这个locals字典。

通常情况下,如果你在顶层定义一个函数,locals和globals是一样的,所以函数可以在其中可见,因为它们能看到全局的函数。

当一个函数在另一个函数的范围内定义时,Python会注意到它在函数内被访问,并创建一个闭包,这样“horse”就会映射到外部范围中的绑定。

在这里,这是一个奇怪的中间情况。exec表现得好像定义是在顶层,所以没有创建闭包。然而,由于locals和globals并不相同,定义并没有去到函数可以访问的地方——它只是在不可访问的外部locals字典中定义。

你可以做几件事情:

  1. 使用同一个字典作为locals和globals。也就是说,"exec s in locals(),locals()"(或者更好,直接使用你自己的字典)。只提供一个globals()字典也有相同的效果——例如"exec s in mydict"。
  2. 把函数放在自己的函数里面,这样就会创建一个闭包。例如:

    s="""
    def go():
        def factorial(x):
            if x==0: return 1
            return x*factorial(x-1)
        print factorial(10)
    go()"""
    
  3. 通过添加“global funcname”指令,强制函数进入globals()而不是locals,如stephan的回答所建议的。

5

对我来说,这个方法有效:

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

rec(5)"""

exec(a)
5
6
7
8
9
10

我只能说,你的代码里可能有个错误。

补充说明

给你看看这个:

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

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

fn1()

撰写回答