全局变量扰乱了我的递归函数

时间:2012-05-29 18:24:29

标签: python recursion global-variables

我刚遇到一个棘手的问题。以下代码应该将单词拆分为长度为numOfChar的块。该函数调用自身,这使得无法在函数内部生成结果列表(res)。但是如果我把它作为全局变量保留在外面,那么每次后续调用具有不同输入值的函数都会导致错误的结果,因为res没有被清除。

任何人都可以帮助我吗?

这是代码 (如果您有兴趣,这是problem 7-23 from PySchools.com):

res = []

def splitWord(word, numOfChar):        
    if len(word) > 0:
        res.append(word[:numOfChar])
        splitWord(word[numOfChar:], numOfChar)    
    return res

print splitWord('google', 2)
print splitWord('google', 3)
print splitWord('apple', 1)
print splitWord('apple', 4)

3 个答案:

答案 0 :(得分:5)

纯递归函数不应该修改全局状态,这算作副作用。

不要追加和递归,而是试试这个:

def splitWord(word, numOfChar): 
    if len(word) > 0:
        return [word[:numOfChar]] + splitWord(word[numOfChar:], numOfChar)
    else:
        return []

在这里,你将这个单词一次一块地切成碎片,然后在每次通话的同时下去,然后在上升时将这些碎片重建成一个列表。

这是一种名为tail recursion的常见模式。

P.S。正如@e-satis所述,递归不是在Python中执行此操作的有效方法。另请参阅@e-satis's answer以获得更精细的尾递归示例,以及使用生成器解决问题的更多Pythonic方法。

答案 1 :(得分:2)

这里完全不需要递归:

def splitWord(word, numOfChar):
   return [word[i:i+numOfChar] for i in xrange(0, len(word), numOfChar)]

如果你坚持使用递归解决方案,那么最好避免使用全局变量(它们使得真的难以理解正在发生的事情)。这是一种方法:

def splitWord(word, numOfChar):
   if len(word) > 0:
      return [word[:numOfChar]] + splitWord(word[numOfChar:], numOfChar)
   else:
      return []

答案 2 :(得分:2)

详细说明@Helgi答案,这是一个更高效的递归实现。它更新列表而不是对两个列表求和(这导致每次都创建一个新对象)。

此模式强制您将列表对象作为第三个参数传递。

def split_word(word, num_of_chars, tail):

    if len(word) > 0:
        tail.append(word[:num_of_chars])
        return split_word(word[num_of_chars:], num_of_chars, tail)

    return tail

res = split_word('fdjskqmfjqdsklmfjm', 3, [])

这种形式的另一个优点是它允许尾递归优化。它在Python中没用,因为它不是一种执行这种优化的语言,但如果你把这段代码翻译成Erlang或Lisp,你就可以免费获得它。

请记住,在Python中,你受到递归堆栈的限制,并且没有办法解决它。这就是递归不是首选方法的原因。

您最有可能使用yielditertools(一个模块来操纵生成器)来使用生成器。这是一个可以拆分任何可迭代块的函数的一个很好的例子:

from itertools import chain, islice

def chunk(seq, chunksize, process=iter):
    it = iter(seq)
    while True:
        yield process(chain([it.next()], islice(it, chunksize - 1)))

如果你开始学习Python,现在有点复杂,所以我不希望你现在完全掌握它,但你可以看到它并且知道它存在是很好的。稍后你会再回过头来看看(我们都做过,Python迭代工具最初都是压倒性的。)

这种方法的好处是:

  • 它可以嵌套任何可迭代的,不仅仅是字符串,还有列表,字典,元组,流,文件,集,查询集,你可以命名它......
  • 它接受任意长度的迭代,甚至是一个长度未知的迭代(想想这里的字节流)。
  • 它占用的内存非常少,因为生成器的最佳功能是它们一个接一个地生成动态值,并且在计算下一个结果之前它们不存储先前的结果。
  • 它会返回任何性质的块,这意味着你可以拥有一大块x个字母,x个项目列表,甚至生成器吐出x个项目(这是默认值)。
  • 它返回一个生成器,因此可以用于其他生成器的流程。将数据从一个生成器管道化到另一个生成器,bash样式,是一种很棒的Python功能。

要获得与您的功能相同的结果,您可以:

In [17]: list(chunk('fdjskqmfjqdsklmfjm', 3, ''.join))
Out[17]: ['fdj', 'skq', 'mfj', 'qds', 'klm', 'fjm']