[英]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
出现在函数中,结果不是单个项目,而是可迭代的 - 即使序列中只出现一个项目。 所以当你传递1
来flatten
,结果就是一个单项生成器 。 要从中获取项目,您仍需要迭代它。
由于这个单项生成器是可迭代的,当内部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
部分并生成nested
的1
。
如果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.