Home > Software design >  Iterator behaves well when subclassing from list, but not from deque - Python
Iterator behaves well when subclassing from list, but not from deque - Python

Time:03-22

I would like to create a "circular list" object: one through which I could iterate, cyclically, forever. To that effect, I attempted to subclass the list class as such:

from itertools import cycle 

class Circle(list):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def __iter__(self):
        sup_iter = super().__iter__()
        return cycle(sup_iter)

And, indeed, it works wonderfully. If, however, I attempt to base my class on the deque class, I can seemingly no longer

  • call my str or repr on my objects
  • convert them to lists

as doing so makes the python interpreter freeze, and the process eventually gets killed. Bear in mind that none of it happens when the class inherits from list.

I am at a loss, can anyone help shed some light into what's going on?

CodePudding user response:

Of course you can't call list on an infinite iterator. The definition of list on an iterator is that it keeps on reading elements until the iterator tells it there are no more.

Do you know about itertools.cycle?

CodePudding user response:

Both converting them to lists and calling repr (which will try to convert it to a list in the process of building a repr string) will attempt to iterate over the deque.

But you've "broken" this by making iteration never end.

One might say that the code that does this (eventually, here) could look at the length of the object (if it had one, not all would) and stop after that many elements, but it does not:

    /* Run iterator to exhaustion. */
    for (;;) {
        PyObject *item = iternext(it);
        ...

IMO, you're better off leaving __iter__ as-is and using cycle(your_circle) where you want to iterate over your circular list.

CodePudding user response:

The list repr in CPython is defined here: https://github.com/python/cpython/blob/v3.10.3/Objects/listobject.c#L361-L415

The important point is that this loop uses

for (i = 0; i < Py_SIZE(v);   i) { ...

and you don't override the size of the object at all, so a Circle([1,2,3]) still has 3 elements as far as the list repr is concerned. The loop does a repr on each element, and then finishes.

The deque repr in CPython is defined here: https://github.com/python/cpython/blob/v3.10.3/Modules/_collectionsmodule.c#L1376-L1404

The important point is that this code uses

aslist = PySequence_List(deque);

i.e. it first represents the deque as a list, which for your Circle class will be an infinite loop, eventually consuming all memory available and appearing to freeze your computer.

  • Related