10

我是一名编程新手,在理解我的 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() 的修改版本进行测试让我确信事实并非如此。相反,似乎“产量元素”线是负责任的。

也就是说,我的问题是……“屈服元素”如何真正执行?看起来“嵌套”要么是一个列表——在这种情况下会添加另一层递归——或者它是一个数字,你会得到一个 TypeError。

对此的任何帮助将不胜感激......特别是,我很想了解事件链,因为 flatten() 处理了一个简单的示例,例如:

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

5 回答 5

12

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

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
于 2012-07-07T17:47:06.747 回答
6

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

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

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

考虑这个问题的另一种方法是说,每次您将不可迭代的值传递给 时flatten,它都会将该值包装在一个可迭代的单项中并“返回”它。

于 2012-07-07T18:33:12.843 回答
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不再是可迭代的。这应该很有趣……</p>

(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,所以一旦我们的最低帧命中 a TypeError,结果就会一直传播到堆栈的最外层帧flatten,这会在移动到外部可迭代的其他部分之前将其产生给外部世界。

于 2012-07-07T17:51:53.327 回答
1

try except构造为您捕获了异常并nested返回,这正是给出的论点flatten()

所以flatten (1) 会出错,并for sublist in nested:继续处理.exceptnested1

于 2012-07-07T17:39:38.717 回答
1

yield element可以执行 ifnested是一个列表但sublist不是(即,如果nested是一个普通的“平面”列表)。在这种情况下,for sublist in nested可以正常工作。当下一行递归调用flatten sublist时,当递归调用尝试迭代“子列表”(不可迭代)时,将引发 typerror。这个 TypeError 被捕获,递归调用返回整个输入列表,因此它被for element in flatten(sublist)调用迭代。换句话说,如果for element in flatten(sublist)sublistfor element in sublist已经平坦,则结束。

要认识到的关键是,即使是非嵌套列表也会导致递归调用。一个like 调用flatten([1])会产生两个yield:递归调用将yield[1]到外部调用,而外部调用立即重新yields 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]
于 2012-07-07T17:49:49.190 回答