Confusing […] List in Python: What is it?

Posted on

Question :

Confusing […] List in Python: What is it?

So I was writing up a simple binary tree in Python and came across […]

I don’t believe this to be related to the Ellipsis object, more it seems to have something to do with an infinity loop (due to Python’s shallow copy?). The source of this infinity loop and why it doesn’t get expanded while expanding when accessed is something I’m completely lost to, however

>>> 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, [...], [...]]

Version using 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

version using [a,b]

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

So what exactly is […]?

Answer #1:

It can also appear if you have a circular structure with a list pointing to itself. Like this:

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

Since python can’t print out the structure (it would be an infinite loop) it uses the ellipsis to show that there is recursion in the structure.


I’m not quite sure if the question was what what going on or how to fix it, but I’ll try to correct the functions above.

In both of them, you first make two recursive calls, which add data to the list y, and then AGAIN append the returned data to y. This means the same data will be present several times in the result.

Either just collect all the data without adding to any y, with something like

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

or just do the appending in the calls, with something like

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

(Of course, both these snippets need handling for empty lists etc)

@Abgan also noted that you really don’t want y=[] in the initializer.

Answered By: Demur Rumed

Answer #2:

I believe, that your ‘tree’ contains itself, therefore it contains cycles.

Try this code:

   a = [1,2,3,4]
   print a
   a.append(a)
   print a

The first print outputs:

  [1,2,3,4]

while the second:

  [1,2,3,4, [...]]
 

The reason is using

 def Keys(x,y=[]):
 

This is wrong and evil. List is a mutable object, and when used as a default parameter, it is preserved between function calls.
So each y += “anything” operation adds to the same list (in all function calls, and since the function is recursive…)


See the Effbot or Devshed for more details on mutable objects passed as default values for functions.

Answered By: CAdaker

Answer #3:

I don’t understand your code above, but the […] I think is the Python interpreter skipping infinite data structures. For example:

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

It looks like your tree structure is becoming looped.

The answers about slice objects are beside the point.

Answered By: Abgan

Answer #4:

I don’t believe this to be related to the Ellipsis object, more it seems to have something to do with an infinity loop (due to Python’s shallow copy?). The source of this infinity loop and why it doesn’t get expanded while expanding when accessed is something I’m completely lost to, however

Look at the following code:

>>> a = [0]
>>> a.append(a)
>>> print a
[0, [...]]

How is Python supposed to print a? It is a list that contains a zero and a reference to itself. Hence it is a list that contains a zero and a reference to a list

[0, [...]]

which in turn contains a zero and a reference to a list

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

which in turn contains a zero and a reference to a list,
and so on, recursively:

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

There is nothing wrong with the recursive data structure itself. The only problem is that it cannot be displayed, for this would imply an infinite recursion. Hence Python stops at the first recursion step and deals with the infinity issue printing only the ellipsis, as was pointed out in previous answers.

Answered By: Ned Batchelder

Answer #5:

If you would have used a PrettyPrinter, the output would had been self explanatory

>>> 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

In other words its something like

enter image description here

Answer #6:

EDIT: As mentioned above, this isn’t the Ellipsis object, but the result of a looped list. I jumped the gun here. Knowing about the Ellipsis object is a good bit of back shelf knowledge should you find an Ellipsis in some actual code, rather than the output.


The Ellipsis object in Python is used for extended slice notation. It’s not used in current Python core libraries, but is available for developers to define in their own libraries. For example, NumPy (or SciPy) use this as part of their array object. You’ll need to look at the documentation for tree() to know exactly how Ellipsis behaves in this object.

From Python documentation:

3.11.8 The Ellipsis Object

This object is used by extended slice
notation (see the Python Reference
Manual). It supports no special
operations. There is exactly one
ellipsis object, named Ellipsis (a
built-in name).

It is written as Ellipsis.

Answered By: Abhijit

Answer #7:

Ok, so in points:

  1. You’re creating infinite data
    structure:

    def Keys(x,y=[])

    will use the same ‘y’ in
    each call. This just isn’t correct.

  2. The print statement, however, is clever enough not to print an infinite data, but to mark self-reference with a […] (known as Ellipsis)

  3. The Python will allow you to address such structure correctly, so you can write
    a.keys()[1][1][1]

    and so on. Why shouldn’t you?

  4. The y = y[:] statement simply copies the list y. Can be done more soundly with y = list(y)

Try using the following code:

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

But still I guess that it can bite you. You’re still using the same variable y (I mean the same object) in three places in one expression:

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

Is that what you really want to achieve?
Or maybe you should try:

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 []
Answered By: Jarred McCaffrey

Answer #8:

For the difference between the two versions of the function Keys, note the following difference:

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

The right side value in this statement is a list which contains x[2], plus the ELEMENTS OF Keys(x[0],y) and the ELEMENTS OF Keys(x[1],y)

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

The right side value in this statement is a list which contains x[2], plus the LIST Keys(x[2],y) and the LIST Keys(x[1],y).

So the version using [a,b] will causing y contains itself as its elements.

Some other notes:

  1. Since in python, the default value object is created once when the function is defined, the first version will not work like the example shows. It will contain multiple copy of some keys. It’s hard to explain in short, but you can get some idea by printing the values of x, y on each call of Keys.

    This is confirmed by running the function on my machine with python 2.5.2.

  2. Also because the default value is defined only once at function definition time, even the function works correct for the first time, it will not work when calling with a different a, since the keys in the first binary tree will remain in y.

    You can see this by calling Keys(a) twice, or calling it on two different lists.

  3. The second parameter is not required for this problem. The function can be like this:

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

    Defining a recursive function basically contains two part, solve subproblems and combined the results. In your code, the combining results part is repeated twice: one by accumulating them in y, one by adding the list together.

Answered By: Abgan

Leave a Reply

Your email address will not be published.