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