Search code examples
pythonclassiterationnodes

next(iterable) works but 'for' loops just creates an infinite loop


I'm trying to recursively iterate through all my nodes, however I get an infinite loop.


class Dictionary:
    
    # STARTS CLASS NODE ################################################
    class Node:
        def __init__(self, key, value):
            self.key = key
            self.value = value
            self.nextNode = None

        def insert(self, key, value):
            if self.nextNode is None or key < self.nextNode.key:
                new = Dictionary.Node(key, value)
                new.nextNode = self.nextNode
                self.nextNode = new
            elif key > self.nextNode.key:
                self.nextNode.insert(key, value)
            else:
                pass

        def __iter__(self):
            if self.nextNode is None:
                return self
            else:
                return self.__next__().__iter__()

        def __next__(self):
            return self.nextNode

        def __str__(self):
            return f'{self.key} : {self.value}'
    # ENDS CLASS NODE ################################################

    def __init__(self):
        self.__head = None

    def insert(self, key, value):
        if self.__head is None or key < self.__head.key:
            new = self.Node(key, value)
            new.nextNode = self.__head
            self.__head = new
        elif key > self.__head.key:
            self.__head.insert(key, value)
        else:
            pass

    def __iter__(self):
        return self.__head

    def __str__(self):
        return str(self.__head)


d = Dictionary()
d.insert(2, 2)
d.insert(3, 3)
d.insert(4, 4)
d.insert(5, 5)

When I do:

p = iter(d)
print(p)
print(next(p))
print(next(next(p))

it works, but when I do

for i in p:
    print(i)

I get an infinite loop that prints 3s. It doesn't even jump nodes.

Isn't a for loop just the same thing as doing iter() and then using next() a bunch of times until I hit a condition (None)? Shouldn't I be getting the same thing for these? What am I doing wrong?


Solution

  • Your test code:

    p = iter(d)
    print(p)
    print(next(p))
    print(next(next(p)))
    

    is not what happens in a for-loop.

    That would be more like this:

    it = iter(d)
    print(next(it))
    print(next(it))
    ... 
    

    and using your class, this would just give you the same element over and over again.

    An iterator is an object that you call next on repeatedly and it gives you a sequence of elements, until it raises an exception to signal that it is finished. Your __next__ implementation just returns self.nextNode each time it is called. It won't ever raise an exception, so there's no signal that the iteration is finished.

    If you want to write an iterator explicitly, you would need to create and update an object that keeps track of how far you had got through the iteration, which could be something like this:

    class NodeIter:
        def __init__(self, start):
            self.cur = start
        def __iter__(self):
            return self
        def __next__(self):
            if self.cur is None:
                raise StopIteration()
            x = self.cur
            self.cur = x.nextNode
            return x
    

    And then in your Dictionary you would have:

        def __iter__(self):
            return NodeIter(self.__head)
    

    Alternatively, here is a quick way to write __iter__ that would go through all the nodes:

    def __iter__(self):
        cur = self.__head
        while cur is not None:
            yield cur
            cur = cur.nextNode
    

    This is a generator function, which is an easy way to generate a sequence using yield statements.