2 回答
TA贡献1815条经验 获得超10个赞
该函数使用递归再次调用自身。这个想法是你将一个更大的问题分解成更小的部分,每个部分独立解决,然后结合结果来解决更大的问题。
在这里,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
设定为3
type(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"
。但是您可能会看到,在这种详细程度的情况下,事情有点过于冗长,无法一次性完成。
TA贡献1804条经验 获得超7个赞
如果写得更简单,也许会更容易理解:
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]
添加回答
举报