在Python中递归地展开嵌套列表

3
我在 Python 中尝试使用生成器玩耍,并尝试利用简单的递归方案来实现flatten函数。即,一个接受可能包含子列表的列表作为输入,并输出只迭代输入的原子元素的可迭代对象的函数。
所以,print(list(flatten([1,2,3,[4,5,6]])))应该返回包含[1,2,3,4,5,6]的东西。
我的尝试如下:
def flatten(toflatten):
    try:
        for element in toflatten:
            flatten(element)
    except TypeError:
        yield toflatten

因此,它应该检查其参数是否为可迭代对象。如果是这种情况,则还要对此对象进行递归。否则,将其作为单个元素产生。
这并不起作用,flatten([1,2,3,[4,5,6]])仅返回一个空列表。
为什么会这样呢?特别是,为什么它甚至没有在此输入上执行递归函数调用?(我正在使用Python 3.5)

首先,你期望 flatten(element) 做什么?这行代码既不返回/生成任何内容,也不改变任何数据结构,因此它是无意义的。 - timgeb
如果您的最终目标是学习递归,我知道这可能没有帮助,但是尝试使用sum(yourlist,[])来展平您的列表。我建议在最后返回您的列表。 - Tomos Williams
@TomosWilliams 不可以将整数和列表连接起来。 - timgeb
@ChuckSchuldiner 考虑函数 foo(x): return 2*x。为什么你会期望一个只包含 foo(x) 的行是有用的呢?输入参数没有被改变,而且你也没有使用结果。 - timgeb
@timgeb 哦,我现在明白你的意思了!感谢(所有人)的评论。我以为递归函数调用中的yield语句会传递到较低的递归级别。所以显然这不是这种情况。有没有一种优雅的方法来解决这个问题? - ChuckSchuldiner
显示剩余4条评论
1个回答

6

所以,你想要将一个列表压平。你已经走上了正确的道路,但是犯了几个错误。以下是这些错误。

  1. 将你的 try-except 语句移动到循环体内部。在你的代码中,如果一个元素引发了 TypeError 异常,则循环停止运行。你不希望出现这种情况。

  2. try 中,你没有产生任何结果。只是调用了一个函数。你应该在那里返回一些东西。如果你使用的是 Python3.3+,我建议你使用 yield from

  3. 最后,在 except 中,你需要 yield element,而不是 toflatten。不要生成整个列表。

def flatten(toflatten):    
   for element in toflatten:
       try:
           yield from flatten(element)
       except TypeError:
           yield element

这给出了以下结果,

>>> list(flatten([1,2,3,[4,5,6]]))
[1, 2, 3, 4, 5, 6]

您已经使用了EAFP(宁愿请求原谅,也不要事先获得许可)方法,这很好。这是一种做法(其实是我最喜欢的方法),但存在一个缺点:这会在字符串上崩溃。
还有另一种方法:LYBL(先看后跳)。它更加谨慎,使用if语句来避免出现错误。
def flatten(toflatten):    
   for element in toflatten:
       if isinstance(element, list):
           yield from flatten(element)
       else:
           yield element

这与以前的工作方式相同,并提供以下内容:

>>> list(flatten([1,2,3,[4,5,6]]))
[1, 2, 3, 4, 5, 6]

然而,这是有好处的,因为只在子列表上调用yield from生成器委派。我提到它也适用于字符串元素了吗?
>>> list(flatten([1,2,3,[4,5,'abc']]))
[1, 2, 3, 4, 5, 'abc']

请注意,在两种情况下,如果您有递归定义的列表,则存在无限递归的警告。例如,flatten 将崩溃并显示此类输入。
x = [1, 2, 3]
x.append(x)

flatten(x)

你最终会遇到一个运行时错误:

RuntimeError: maximum recursion depth exceeded

您可能还需要考虑“不需要”的可迭代对象,例如字符串,如果您特别挑剔,可以考虑递归的 toflatten 可迭代对象。(例如 toflatten = []; toflatten = [toflatten] - timgeb
2
@timgeb提到EAFP在处理字符串时会失败,而两者在递归列表方面也会失败。感谢您的评论! - cs95
太棒了,所有问题都解决了,非常感谢!(我之前还不熟悉yield-from语句,这正是我一直在寻找的) - ChuckSchuldiner
抱歉,新的Python用户+新的Stack Overflow用户 ;) - ChuckSchuldiner
1
看清楚,再跳。 - mcalex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接