用于展平嵌套列表的递归生成器

时间:2012-07-07 17:32:29

标签: python recursion

我是一个编程新手,我在理解我的python教科书(Magnus Lie Hetland的“Beginning Python”)中的一个例子时遇到了一些麻烦。这个例子是一个递归生成器,用于展平嵌套列表的元素(具有任意深度):

def flatten(nested):
    try:
        for sublist in nested:
            for element in flatten(sublist):
                yield element
    except TypeError:
        yield nested

然后您将按如下方式输入嵌套列表:

>>> list(flatten([[[1],2],3,4,[5,[6,7]],8]))
[1,2,3,4,5,6,7,8]

我理解flatten()中的递归如何帮助减少到此列表的最内层元素'1',但我不明白当'1'实际传递回flatten()时会发生什么作为'嵌套'。我认为这会导致TypeError(不能迭代一个数字),并且异常处理实际上是用于生成输出的繁重工作......但是使用flatten()的修改版本进行测试已经说服了我事实并非如此。相反,似乎'yield element'行负责。

那就是说,我的问题是......如何“屈服元素”实际上被执行?似乎'嵌套'将是一个列表 - 在这种情况下添加另一层递归 - 或者它是一个数字,你得到一个TypeError。

任何对此的帮助都会非常感激...特别是,我喜欢在事件链中走动,因为flatten()会处理一个简单的例子:

list(flatten([[1,2],3]))

5 个答案:

答案 0 :(得分:13)

我在函数中添加了一些工具:

def flatten(nested, depth=0):
    try:
        print("{}Iterate on {}".format('  '*depth, nested))
        for sublist in nested:
            for element in flatten(sublist, depth+1):
                print("{}got back {}".format('  '*depth, element))
                yield element
    except TypeError:
        print('{}not iterable - return {}'.format('  '*depth, nested))
        yield nested

现在致电

list(flatten([[1,2],3]))

显示器

Iterate on [[1, 2], 3]
  Iterate on [1, 2]
    Iterate on 1
    not iterable - return 1
  got back 1
got back 1
    Iterate on 2
    not iterable - return 2
  got back 2
got back 2
  Iterate on 3
  not iterable - return 3
got back 3

答案 1 :(得分:6)

或许你的困惑之一就是你正在考虑最终的yield语句,好像它是return语句。实际上,有几个人建议在此代码中抛出TypeError时,传递的项目将“返回”。事实并非如此!

请记住,任何时候yield出现在函数中,结果都不是单个项目,而是 iterable - 即使序列中只出现一个项目。因此,当您将1传递给flatten时,结果就是一项生成器。要从中获取项目,您仍需要迭代它。

由于这个单项生成器是可迭代的,当内部TypeError循环尝试迭代它时,它不会抛出for;但内部for循环只执行一次。然后外部for循环移动到嵌套列表中的下一个iterable。

考虑这个问题的另一种方法是,每当你将一个不可迭代的值传递给flatten时,它就会将值包装在一个可迭代的项目中并“返回”它。

答案 2 :(得分:4)

分解你通常理解的函数的一个很好的方法,但是有一小部分是难以理解的,就是使用python调试器。这里添加了评论:

-> def flatten(nested):
(Pdb) l
  1  -> def flatten(nested):
  2         try:
  3             for sublist in nested:
  4                 for element in flatten(sublist):
  5                     yield element
  6         except TypeError:
  7             yield nested
  8     
  9     import pdb; pdb.set_trace()
 10     list(flatten([[1,2],3]))
 11     
(Pdb) a
nested = [[1, 2], 3]

上面,我们刚刚输入了函数,参数是[[1, 2], 3]。让我们使用pdb的 step 函数将函数逐步调入我们应该遇到的任何递归调用:

(Pdb) s
> /Users/michael/foo.py(2)flatten()
-> try:
(Pdb) s
> /Users/michael/foo.py(3)flatten()
-> for sublist in nested:
(Pdb) s
> /Users/michael/foo.py(4)flatten()
-> for element in flatten(sublist):
(Pdb) s
--Call--
> /Users/michael/foo.py(1)flatten()
-> def flatten(nested):
(Pdb) a
nested = [1, 2]

我们已进入flatten的一个内框,参数为[1, 2]

(Pdb) s
> /Users/michael/foo.py(2)flatten()
-> try:
(Pdb) s
> /Users/michael/foo.py(3)flatten()
-> for sublist in nested:
(Pdb) s
> /Users/michael/foo.py(4)flatten()
-> for element in flatten(sublist):
(Pdb) s
--Call--
> /Users/michael/foo.py(1)flatten()
-> def flatten(nested):
(Pdb) a
nested = 1

两个帧,参数1不再是可迭代的。这应该很有趣......

(Pdb) s
> /Users/michael/foo.py(2)flatten()
-> try:
(Pdb) s
> /Users/michael/foo.py(3)flatten()
-> for sublist in nested:
(Pdb) s
TypeError: "'int' object is not iterable"
> /Users/michael/foo.py(3)flatten()
-> for sublist in nested:
(Pdb) s
> /Users/michael/foo.py(6)flatten()
-> except TypeError:
(Pdb) s
> /Users/michael/foo.py(7)flatten()
-> yield nested
(Pdb) s
--Return--
> /Users/michael/foo.py(7)flatten()->1
-> yield nested

好的,所以由于except TypeError,我们只是屈服于论证本身。向上一帧!

(Pdb) s
> /Users/michael/foo.py(5)flatten()
-> yield element
(Pdb) l
  1     def flatten(nested):
  2         try:
  3             for sublist in nested:
  4                 for element in flatten(sublist):
  5  ->                 yield element
  6         except TypeError:
  7             yield nested
  8     
  9     import pdb; pdb.set_trace()
 10     list(flatten([[1,2],3]))
 11     

yield element当然会产生1,所以一旦我们的最低帧到达TypeError,结果就会一直向上传播到最外面的flatten帧,在进入外部可迭代的其他部分之前,它将它产生于外部世界。

答案 3 :(得分:1)

try except构造为您捕获了异常并返回nested,这只是flatten()的参数。

如此展平(1)将在for sublist in nested:中出错并继续except部分,并产生nested 1

答案 4 :(得分:1)

如果yield element是列表但nested不是(sublist是正常的"扁平"列表),则可以执行

nested 。在这种情况下,for sublist in nested将正常工作。当下一行递归调用flatten sublist时,当递归调用试图迭代"子列表时,将引发typerror" (这是不可迭代的)。捕获此TypeError并且递归调用返回整个输入列表,因此它将被for element in flatten(sublist)调用迭代。换句话说,如果子列表已经持平,则for element in flatten(sublist)会结束for element in sublist

要识别的关键是即使是非嵌套列表也会导致递归调用。像flatten([1])之类的调用会产生两个收益:递归调用会向外部调用产生[1],外部调用会立即重新产生1

此版本的功能可能有助于了解正在发生的事情:

    def flatten(nested, indent=""):
        try:
            print indent, "Going to iterate over", nested
            for sublist in nested:
                print indent, "Going to iterate over flattening of", sublist
                for element in flatten(sublist, indent+"  "):
                    print indent, "Yielding", element
                    yield element
        except TypeError:
            print indent, "Type Error!  Yielding", nested
            yield nested

    >>> list(flatten([[1,2],3]))
     Going to iterate over [[1, 2], 3]
     Going to iterate over flattening of [1, 2]
       Going to iterate over [1, 2]
       Going to iterate over flattening of 1
         Going to iterate over 1
         Type Error!  Yielding 1
       Yielding 1
     Yielding 1
       Going to iterate over flattening of 2
         Going to iterate over 2
         Type Error!  Yielding 2
       Yielding 2
     Yielding 2
     Going to iterate over flattening of 3
       Going to iterate over 3
       Type Error!  Yielding 3
     Yielding 3
    [1, 2, 3]