0
class Node:
    def __init__(self, value):
         self._value = value
         self._children = []
    def __repr__(self):
         return 'Node({!r})'.format(self._value)
    def add_child(self, node):
         self._children.append(node)
    def __iter__(self):
         return iter(self._children)
    def depth_first(self):
         yield self
         for c in self:
             yield from c.depth_first()
if __name__ == '__main__':
    root = Node(0)
    child1 = Node(1)
    child2 = Node(2)
    root.add_child(child1)
    root.add_child(child2)
    child1.add_child(Node(3))
    child1.add_child(Node(4))
    child2.add_child(Node(5))

for a in root.depth_first():
    print(a)
# Outputs Node(0), Node(1), Node(3), Node(4), Node(2), Node(5)

I thought that a list is an object that we can iterate over it so why use iter() ? I am new in python so this look to me so weird.

Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253
Abkaa z
  • 3
  • 1
  • 3

2 Answers2

1

Because returning self._children returns a list object, which doesn't work as an iterator, remember, iterators implement the __next__ method in order to supply items during iteration:

>>> next(list())  # doesn't implement a __next__ method
TypeError: 'list' object is not an iterator

lists are iterable, they can be iterated through because calling iter on the will return an iterator, but, lists themselves are not iterators -- a good breakdown of these can be found in the top answer of this Question.

A lists __iter__ method returns a custom new list_iterator object each time __iter__ is called:

list().__iter__()
Out[93]: <list_iterator at 0x7efe7802d748>

and, by doing that, supports iteration multiple times, the list_iterator object implements the __next__ method that's required. Calling and returning iter on it will just do that, return the lists iterator and save you the trouble of having to implement __next__.


Addressing your comment as for why for c in self._children works, well, because it is essentially doing the same thing. What basically happens with the for loop is:

it = iter(self._children)  # returns the list iterator
while True:
    try:
        i = next(it)
        <loop body>
    except StopIteration:
        break

meaning, iter is called again on the list object and is used by the for loop for next calls.

Community
  • 1
  • 1
Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253
  • i did't understand what do you mean by returning a list object ? and when i tried this code it worked well `class Node: def __init__(self, value): self._value = value self._children = [] def __repr__(self): return 'Node({!r})'.format(self._value) def add_child(self, node): self._children.append(node) def __iter__(self): return iter(self._children) def depth_first(self): yield self for c in self._children: yield from c.depth_first()` – Abkaa z Aug 07 '16 at 21:24
  • why when i change( for c in self:) with (for c in self._children) it work very well – Abkaa z Aug 07 '16 at 21:32
0

Every iterable object implements an iter() function that returns itself. the iter(obj) calls the obj.iter(), which is same as returning the list object in your example.