混乱的[...]列表在Python中是什么?

17

我正在用Python编写一个简单的二叉树,但在此过程中遇到了问题[...]。

我不认为这与省略号(Ellipsis object)有关,更可能是与无限循环有关(由于Python的浅拷贝?)。这个无限循环的源头以及为什么它在访问时未被展开是我完全不理解的。

>>> a
[[[[[], [], 8, 3], [[], [], 3, 2], 6, 3], [], 1, 4], [[], [], -4, 2], 0, 0]
>>> Keys(a)#With a+b
[0, 1, 6, 8, 3, -4]
>>> Keys(a)#With [a,b]
[8, [...], [...], 3, [...], [...], 6, [...], [...], 1, [...], [...], -4, [...], [...], 0, [...], [...]]
>>> Keys(a)[1]#??
[8, [...], [...], 3, [...], [...], 6, [...], [...], 1, [...], [...], -4, [...], [...], 0, [...], [...], 8, [...], [...], 3, [...], [...], 6, [...], [...], 1, [...], [...], -4, [...], [...], 0, [...], [...]]

使用a+b的版本

def Keys(x,y=[]):
    if len(x):y+=[x[2]]+Keys(x[0],y)+Keys(x[1],y)#Though it seems I was using y=y[:]+, this actually outputs an ugly mess
    return y

使用 [a,b] 版本

def Keys(x,y=[]):
    if len(x):y+=[x[2],Keys(x[0],y),Keys(x[1],y)]
    return y

那么,究竟什么是 [...]?

9个回答

25

如果您有一个指向自身的列表,就会出现这种情况。例如:

>>> a = [1,2]
>>> a.append(a)
>>> a
[1, 2, [...]]
>>> 

因为Python无法打印出结构(它将成为一个无限循环),所以使用省略号来显示结构中存在递归。


我不太确定问题是关于正在发生什么还是如何修复它,但是我会尝试纠正上面的函数。

在这两个函数中,你首先进行了两次递归调用,将数据添加到列表y,然后再将返回的数据再次追加到y中。这意味着相同的数据将出现多次在结果中。

要么只收集所有数据而不向任何y添加,例如:

return [x[2]]+keys(x[0])+keys(x[1])

或者在函数调用中直接进行追加,例如:

y += [x[2]]
keys(x[0], y) #Add left children to y...
keys(x[1], y) #Add right children to y...
return y

(当然,这两个代码片段都需要处理空列表等情况)

@Abgan 还指出你真的不想在初始化程序中使用 y=[]


6
我相信你的“树”包含自身,因此它包含循环。
尝试这段代码:
a = [1,2,3,4] print a a.append(a) print a
第一个print输出:
[1,2,3,4]
而第二个输出为:
[1,2,3,4, [...]]
原因是使用
def Keys(x,y=[]):
这是错误和恶劣的。列表是可变对象,当作为默认参数使用时,它在函数调用之间保留下来。 因此,每个y +=“anything”操作都会添加到同一个列表中(在所有函数调用中,由于函数是递归的...)

查看EffbotDevshed以获取有关作为函数默认值传递的可变对象更多详细信息。


在[a,b]版本的顶部添加了y=y[:],并获得了输出[[0], [[1], [[6], [[8], [], []], [[3], [], []]], []], [[-4], [], []]]。 - Demur Rumed
现在是我所在的地方凌晨4点27分,所以我的状态不太好,还没有理清这些括号。输出有什么问题吗?而且你应该使用“def Keys(x,y=None):if y is None: y = []”代替。 - Abgan

5

我不理解你上面的代码,但是我认为它是Python解释器跳过无限数据结构。 例如:

>>> a = [0, 1]
>>> a[0] = a
>>> a
[[...], 1]

看起来你的树形结构出现了循环。

关于切片对象的答案与问题无关。


4
我不认为这与省略号对象有关,更可能与无限循环有关(由于Python的浅拷贝?)。无限循环的源头以及为什么在访问时不会被扩展却不断增长是我完全不明白的事情。请看下面的代码:
>>> a = [0]
>>> a.append(a)
>>> print a
[0, [...]]

Python应该如何打印a?它是一个包含零和对自身的引用的列表。因此,它是一个包含零和对列表的引用的列表。
[0, [...]]

这个列表包含一个零和对另一个列表的引用。

[0, [0, [...]]]

其中包含一个零和对列表的引用,以此类推,递归地进行:

[0, [0, [0, [...]]]]
[0, [0, [0, [0, [...]]]]]
[0, [0, [0, [0, [0, [...]]]]]]
...

递归数据结构本身并没有问题。唯一的问题在于它无法被“显示”,因为这将意味着无限递归。因此,Python 在第一次递归步骤处停止,并处理无限问题,仅打印省略号,正如先前的答案所指出的那样。


当您调用a [1]时,您再次获得[0,[...]]。在我的示例中,它似乎返回了一个较大的列表,而不是相同的列表。也许Python正在将[...] + [...]合并为[...]? - Demur Rumed
当您调用a[1]时,您会再次得到[0,[...]]:是的,因为a[1]就是a本身! - Federico A. Ramponi

4
如果您使用了一个PrettyPrinter,输出结果会更加易于理解。
>>> l = [1,2,3,4]
>>> l[0]=l
>>> l
[[...], 2, 3, 4]
>>> pp = pprint.PrettyPrinter(indent = 4)
>>> pp.pprint(l)
[<Recursion on list with id=70327632>, 2, 3, 4]
>>> id(l)
70327632

换句话说,它类似于

enter image description here


2

编辑:如上所述,这不是省略号对象,而是循环列表的结果。我在这里有些冒进。了解省略号对象是一种好的后备知识,如果你在实际代码中找到省略号,而不是输出。


Python中的省略号对象用于扩展切片符号。它没有在当前Python核心库中使用,但可供开发人员在自己的库中定义。例如,NumPy(或SciPy)将其用作其数组对象的一部分。您需要查看tree()文档,以确切了解省略号在此对象中的行为。

来自Python文档

3.11.8 省略号对象

此对象由扩展切片符号(请参见Python参考手册)使用。它不支持任何特殊操作。有一个名为Ellipsis(内置名称)的省略号对象。

它被写成Ellipsis。


1
问题是因为列表中的一个元素引用了列表本身。因此,如果尝试打印所有元素,则永远不会结束。
图示:
x = range(3)
x.append(x)
x[3][3][3][3][3][0] = 5
print x

输出:

[5, 1, 2, [...]]

x[3] 是指向 x 本身。对于 x[3][3] 同样适用。

这可以更好地可视化 在这里


1

好的,所以要点如下:

  1. 你正在创建无限数据结构:

    def Keys(x,y=[])
    将在每次调用中使用相同的“y”。这是不正确的。

  2. 然而,print语句足够聪明,不会打印无限数据,而是用[…](称为省略号)标记自我引用。

  3. Python将允许您正确地处理这种结构,因此您可以编写
    a.keys()[1][1][1]
    等等。为什么不呢?
  4. y = y [:]语句只是复制列表y。可以使用y = list(y)更可靠地完成。

尝试使用以下代码:

def Keys(x,y=None):
    if y is None:
        y = []
    if len(x):
        y += [x[2], Keys(x[0],y), Keys(x[1],y)]
    return y

但我仍然认为它可能会咬你。您仍然在一个表达式中的三个位置使用相同的变量y(我的意思是相同的对象):

y += [x[2], Keys(x[0], y), Keys(x[1], y)]

这是你真正想要实现的吗? 或者你可以尝试:

def mKeys(x, y=None):
    if y is None:
        y = []
    if len(x):
       z = [x[2], mKeys(x[0], y), mKeys(x[1],y)]
       return z
   return []

1

关于函数Keys的两个版本之间的区别,请注意以下差异:

y+=[x[2]]+Keys(x[0],y)+Keys(x[1],y)

这个语句中的右侧值是一个包含x[2]、Keys(x[0],y)的元素以及Keys(x[1],y)的元素的列表。
y+=[x[2],Keys(x[0],y),Keys(x[1],y)]

这个语句中的右侧值是一个列表,其中包含x[2],加上LIST Keys(x[2],y)和LIST Keys(x[1],y)。

因此,使用[a,b]版本将导致y包含自身作为其元素。

其他一些注意事项:

  1. 在Python中,默认值对象是在函数定义时创建的,因此第一个版本不会像示例显示的那样工作。它将包含某些键的多个副本。很难简短地解释,但您可以通过在每次调用Keys时打印x、y的值来获得一些想法。

    在我的机器上使用Python 2.5.2运行该函数已经确认了这一点。

  2. 另外,由于默认值仅在函数定义时定义一次,即使函数第一次正确工作,当使用不同的a调用时也不起作用,因为第一个二叉树中的键将保留在y中。

    您可以通过两次调用Keys(a)或在两个不同的列表上调用它来查看这一点。

  3. 对于这个问题,第二个参数是不必要的。函数可以像这样:

    def Keys(a): if a = []: return [] else: return [a[2]]+Keys(a[0])+Keys(a[1])

    定义递归函数基本上包含两个部分,解决子问题和组合结果。在您的代码中,组合结果部分重复了两次:一次通过在y中累积它们,一次通过将列表相加。


相当优雅。虽然我不得不将a[2]更改为[a[2]]。 - Demur Rumed

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