动漫人物
该函数使用递归再次调用自身。这个想法是你将一个更大的问题分解成更小的部分,每个部分独立解决,然后结合结果来解决更大的问题。在这里,flatten()只要当前序列中包含的元素是列表,就会再次调用自己。这些递归调用一直持续到较小的部分不再包含更多列表。需要记住的是,本地名称newList对于每个函数调用都是本地的。即使flatten()调用自身,每次调用都会产生一个新的、newList独立的本地值。对于您的输入,一个元组:['b', 'a', 'c', 2], [[[3]], 'dog', 4, 5]第一个元素也是一个列表:['b', 'a', 'c', 2]所以这被传递给一个新的flatten()电话。该子列表中没有更多列表,因此该函数所做的就是将每个项目附加到newList列表中并将其作为结果返回。返回后,将flatten()恢复第一个函数,并newList通过extend()调用将返回的列表添加到本地。在您查看 Pythontutor 如何将其可视化的同时,您会注意到原始对象中有很多指向这些列表的指针:您可以看到第一个flatten()调用引用了一个包含两个元素的元组,第二个flatten()调用引用了该元组的第一个元素,即包含的列表。Python 值都存在于称为“堆”的专用内存区域中,名称和列表元素都只是标签、引用、带有附加到这些对象的字符串的名称标签,您可以拥有任意数量的此类标签。请参阅Ned Batchelder 关于该主题的优秀文章。这两个flatten()函数都有自己的newList指向列表对象的引用,当前活动的flatten()函数正忙于从aList它所拥有的引用复制值newList。因此,一旦递归调用flatten()将控制权返回给剩余的仍处于活动状态的flatten()函数。一旦newList使用返回值扩展了本地函数,该函数就会移动到下一个元素[[[3]], 'dog', 4, 5],该元素还有几个列表要处理,首先[[3]],然后[3]没有更多的嵌套列表要处理。如果你用新调用的缩进写出这一切,你会得到:-> flatten((['b', 'a', 'c', 2], [[[3]], 'dog', 4, 5]))newList 设置为空列表item 设定为 ['b', 'a', 'c', 2]type(item) 是一个列表,所以递归-> flatten(['b', 'a', 'c', 2])newList 设置为空列表item设置为'b',不是列表,附加到newList,现在['b']item设置为'a',不是列表,附加到newList,现在['b', 'a']item设置为'c',不是列表,附加到newList,现在['b', 'a', 'c']item设置为2,不是列表,附加到newList,现在['b', 'a', 'c', 2]循环完成,返回 newList<- ['b', 'a', 'c', 2]newList扩展为['b', 'a', 'c', 2],所以现在['b', 'a', 'c', 2]item 设定为 [[[3]], 'dog', 4, 5]type(item) 是一个列表,所以递归-> flatten([[3]])newList 设置为空列表item 设定为 [3]type(item) 是一个列表,所以递归newList扩展为[3],所以现在[3]循环完成,返回 newList<- [3]-> flatten([3])item设置为3,不是列表,附加到newList,现在[3]循环完成,返回 newList<- [3]-> flatten([3])newList 设置为空列表item 设定为 3type(item) 是一个列表,所以递归newList扩展为[3],所以现在[3]循环完成,返回 newList<- [3]-> flatten([[[3]], 'dog', 4, 5])newList 设置为空列表item 设定为 [[3]]type(item) 是一个列表,所以递归newList扩展为[3],所以现在[3]item设置为'dog',不是列表,附加到newList,现在[3, 'dog']item设置为4,不是列表,附加到newList,现在[3, 'dog', 4]item设置为5,不是列表,附加到newList,现在[3, 'dog', 4, 5]循环完成,返回 newList<- [3, 'dog', 4, 5]newList扩展为[3, 'dog', 4, 5],所以现在['b', 'a', 'c', 2, 3, 'dog', 4, 5]<- ['b', 'a', 'c', 2, 3, 'dog', 4, 5]在 Pythontutor 可视化(Pythontutor 用于 Python 代码的默认可视化)中,您看到"b" 两次实际上是 Pythontutor 使用的简化的产物。虽然列表和元组显示为单独的对象,箭头显示它们是如何被引用的,但“原始”类型(例如字符串和整数)显示在列表内或直接在函数框架中的变量内显示。实际上,这些对象也是独立的,它们也存在于堆中并被引用。该"b"值作为单个对象存在,多个列表引用它。但是,您可以选择不同的可视化:使用该选项,可视化变得更大:在这里您可以看到,newList在活动函数框架中和从输入元组引用的原始列表对象都引用了一个str具有 value 的对象"b"。但是您可能会看到,在这种详细程度的情况下,事情有点过于冗长,无法一次性完成。
三国纷争
如果写得更简单,也许会更容易理解:aList = ['b','a','c',2],[[[3]],'dog',4,5] def flatten(value): if not isinstance(value,(list,tuple)) : return [value] return [ item for subItem in value for item in flatten(subItem) ]如果value参数是列表或元组,则连接每个元素以形成扁平输出(第 2 行)。因为这些元素中的每一个本身都可以是一个列表或元组,所以该函数会调用自己在连接到其他元素之前将元素展平。当其参数是标量值(即不是列表或元组)时,该函数将停止调用自身。在这种情况下,它会将值本身作为单个元素列表(第一行)返回,因为它无法进一步展平,并且它的调用者(本身)需要一个列表。flatten( aList ) : returns ['b']+['a']+['c']+[2]+[3]+['dog']+[4]+[5] --> flatten( ['b','a','c',2] ) : returns ['b']+['a']+['c']+[2] --> flatten('b') : returns ['b'] --> flatten('a') : returns ['a'] --> flatten('c') : returns ['c'] --> flatten(2) : returns [2] --> flatten( [[[3]],'dog',4,5] ): returns [3]+['dog']+[4]+[5] --> flatten([[3]]) : returns [3] --> flatten([3]) : returns [3] --> flatten(3) : returns [3] --> flatten('dog') : returns ['dog'] --> flatten(4) : returns [4] --> flatten(5) : returns [5]