繁体   English   中英

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

[英]Recursive generator for flattening nested lists

我是一个编程新手,并且在理解我的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]))

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

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

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

请记住,任何时间yield出现在函数中,结果不是单个项目,而是可迭代的 - 即使序列中只出现一个项目。 所以当你传递1flatten ,结果就是一个单项生成器 要从中获取项目,您仍需要迭代它。

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

考虑这种情况的另一种方法是说,每次传递一个不可迭代的值来flatten ,它会将值包装在一个可迭代的项目中并“返回”它。

分解你通常理解的函数的一个很好的方法,但是有一小部分是难以理解的,就是使用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帧,这会将它传递到外部世界,然后再转移到外部可迭代的其他部分。

try except构造捕获异常并产生nested后退,这只是给flatten()的参数。

所以flatten(1) for sublist in nested:会出错for sublist in nested:并继续使用except部分并生成nested1

如果nested是列表但是sublist不是(即,如果nested是普通的“平面”列表),则可以执行yield element 在这种情况下, for sublist in nested将正常工作。 当下一行递归调用flatten sublist时,当递归调用尝试迭代“子列表”(不可迭代)时,将引发typerror。 捕获此TypeError并且递归调用返回整个输入列表 ,因此它将被for element in flatten(sublist)调用中的for element in flatten(sublist)迭代。 换句话说, for element in flatten(sublist) for element in 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]

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM