理解递归"本地" Python中的变量

时间:2016-04-23 03:54:52

标签: python recursion local-variables

我正在学习递归,并尝试将其应用于一些有趣,易于理解的方式。 (是的,通过三个嵌套的for循环可以更好地完成这一切)

def generate_string(current_string, still_to_place):
    if still_to_place:
        potential_items = still_to_place.pop(0)
        for item in potential_items:
            generate_string(current_string + item, still_to_place)
            #print("Want to call generate_string({}, {})".format(current_string + item, still_to_place))
    else:
        print(current_string)
generate_string("", [['a','b','c'],['d','e','f'],['g','h','i']])

如果我注释掉递归调用并取消注释打印,它会打印出我希望它会调用的内容。然而,只是取消注释打印显示它调用一个空的still_to_place数组,即使它仍然应该有来自"更高的" [d,e,f],[g,h,i]。我想是递归。

我的理解中缺少什么?谢谢!

2 个答案:

答案 0 :(得分:0)

我输出了我在generate_string的每次迭代中得到的东西,这就是我得到的。它可能都令人困惑,因为没有任何表现出你的期望,但让我解释一下Python的想法。

#1st time
current_string = ""
still_to_place = [['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']]

我们首先传递上面的数据,但是,当我们浏览所发生的事情时,我们首先弹出第一个数组['a', 'b', 'c'],然后我们开始迭代这个弹出的数组。但是因为我们调用了.pop(0),所以我们现在只有数组的后半部分,still_to_place.pop(0)在第一次递归调用中生成了generate_string()

#2nd time
current_string = "a"
still_to_place = [['d', 'e', 'f'], ['g', 'h', 'i']]

这正是第一次进行递归调用时current_string和still_to_place中的内容。现在我们将从头开始重新开始执行该功能。我们再次调用pop函数,删除第二个数组['d', 'e', 'f']。现在我们只留下第三个也是最后一个数组。

#3rd time
current_string = "ad"
still_to_place = [['g', 'h', 'i']]

当我们遍历['g', 'h', 'i']时,因为still_to_place现在是空的。 (我们刚刚弹出了最后一个数组。)对generate_string的任何调用都将直接转到else子句,我们将打印出“ad”字符串以及我们刚刚弹出的数组中的值。

#4th, 5th and 6th time
still_to_place = []
current_string = "adg"

still_to_place = []
current_string = "adh"

still_to_place = []
current_string = "adi"

我们现在继续在最后一次递归调用停止的地方,当我们第二次进行时。这是令人困惑的地方。当我们离开current_string = "a"并且still_to_place原来是[['d', 'e', 'f'], ['g', 'h', 'i']]时,我们已经从数组中弹出了所有内容。你看,数组的行为与数字或字符串不同。所有版本的数组共享相同的数据。您更改数据一次,它会在使用该数组的任何位置更改它。 (对象和词典也表现得这样。)

所有这一切都说still_to_place = [],而still_to_place将在剩余的递归调用中保持为空。 potential_items仍然具有弹出['d', 'e', 'f']的数据。我们已经在步骤#4,#5和#6中执行了'd'字符串,所以我们可以完成我们离开的地方

#7th and 8th times
still_to_place = []
current_string = "ae"

still_to_place = []
current_string = "af"

potential_items再次['a', 'b', 'c'],我们已经执行了'a'。与still_to_place不同,potential_items是一个范围较小的局部变量。如果您知道范围的工作原理,那么它将会产生多个potential_items,但它与使用的Still_to_place相同。每次我们从still_to_place弹出一个项目时,我们将弹出的结果添加到具有有限范围的新潜在项目变量。 still_to_place对整个程序来说是全局的,因此对still_to_place的一次更改会导致无法预料的更改。

希望我让事情变得更加混乱,而不是让人感到困惑。对您需要进一步澄清的内容发表评论。

答案 1 :(得分:0)

是的,这是预期的行为。原因是每个函数调用之间共享still_to_place。 Python中的可变对象是“通过赋值传递”,这意味着,如果将列表传递给函数,该函数将共享对SAME列表的引用。 This thread has more detail.

因此,每次调用still_to_place.pop(0)时,都会在每次递归调用中弹出列表。它们都共享相同的列表。

这种行为并不总是令人满意的,通常您希望列表是不可变的。在这种情况下,您需要将递归调用传递给数据结构的修改副本。以下是使用不可变方法的代码:

def generate_string(current_string, still_to_place):
    if still_to_place:
        potential_items = still_to_place[0]
        for item in potential_items:
            generate_string(current_string + item, still_to_place[1:])
            print("Want to call generate_string({}, {})".format(current_string + item, still_to_place))
    else:
        print(current_string)
generate_string("", [['a','b','c'],['d','e','f'],['g','h','i']])

根据经验,对象上的方法(例如.pop)将就地修改它。此外,不同的语言以不同的方式处理可变性,在某些语言中,数据结构总是不可变的。