1

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?

azro
  • 53,056
  • 7
  • 34
  • 70
Jocko
  • 39
  • 1
  • 6
  • 1
    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. – khelwood Oct 10 '20 at 08:55
  • Why do wee see 2 methods init, str, insert in your post ? Please edit and clean the code – azro Oct 10 '20 at 08:56
  • See [How to make a custom object iterable?](https://stackoverflow.com/questions/21665485/how-to-make-a-custom-object-iterable) – martineau Oct 10 '20 at 09:49
  • @azro Main question is: Why is one class defined in the context of another class? That's more than unusual. – Matthias Oct 10 '20 at 10:39

1 Answers1

2

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.

khelwood
  • 55,782
  • 14
  • 81
  • 108
  • Thank you, why does this work without a __next__ method? I thought iter just rejects anything that doesn't have a ____next____ in it? – Jocko Oct 10 '20 at 09:21
  • When the generator function is executed, Python creates an iterator for you with the appropriate methods. The generator is a shortcut. See https://wiki.python.org/moin/Generators – khelwood Oct 10 '20 at 10:16