4

I don't understand exactly why the __iter__ special method just returns the object it's called on (if it's called on an iterator). Is it essentially just a flag indicating that the object is an iterator?

EDIT: Actually, I discovered that "This is required to allow both containers and iterators to be used with the for and in statements." https://docs.python.org/3/library/stdtypes.html#iterator.iter

Alright, here's how I understand it: When writing a for loop, you're allowed to specify either an iterable or an iterator to loop over. But Python ultimately needs an iterator for the loop, so it calls the __iter__ method on whatever it's given. If it's been given an iterable, the __iter__ method will produce an iterator, and if it's been given an iterator, the __iter__ method will likewise produce an iterator (the original object given).

  • 3
    It only returns the same object *if* that object is an iterator. If the object is an *iterable*, then `__iter__` can return some other object that will be the iterator. See [this question](http://stackoverflow.com/questions/9884132/what-exactly-are-pythons-iterator-iterable-and-iteration-protocols). – BrenBarn Sep 09 '15 at 19:35
  • [doc](https://docs.python.org/2/reference/datamodel.html#object.__iter__) – taesu Sep 09 '15 at 19:35
  • But if it's an iterator, I still don't get why you'd have it return itself. –  Sep 09 '15 at 19:39
  • 1
    You need to return an iterator, something you can pass to `next` to get the next object. If the instance implements `__next__`, you can return the instance itself. – jonrsharpe Sep 09 '15 at 19:45
  • 1
    Is your question then "why are iterators iterable"? – BrenBarn Sep 09 '15 at 19:54
  • Compare the difference with & without calling `iter()` on a string: `s='this is a test.';[''.join(u)for u in zip(*[s]*3)];[''.join(u)for u in zip(*[iter(s)]*3)]` – PM 2Ring Sep 09 '15 at 19:57

3 Answers3

3

When you loop over something using for x in something, then the loop actually calls iter(something) first, so it has something to work with. In general, the for loop is approximately equivalent to something like this:

something_iterator = iter(something)
while True:
   try:
       x = next(something_iterator)

       # loop body

   except StopIteration:
       break

So as you already figured out yourself, in order to be able to loop over an iterator, i.e. when something is already an iterator, iterators should always return themselves when calling iter() on them. So this basically makes sure that iterators are also iterable.

poke
  • 369,085
  • 72
  • 557
  • 602
2

This depends what object you call iter on. If an object is already an iterator, then there is no operation required to convert it to an iterator, because it already is one. But if the object is not an iterator, but is iterable, then an iterator is constructed from the object.

A good example of this is the list object:

>>> x = [1, 2, 3]
>>> iter(x) == x
False
>>> iter(x)
<list_iterator object at 0x7fccadc5feb8>
>>> x
[1, 2, 3]

Lists are iterable, but they are not themselves iterators. The result of list.__iter__ is not the original list.

zstewart
  • 2,093
  • 12
  • 24
0

In Python when ever you try to use loops, or try to iterate over any object like below..

Lets try to understand for list object..

>>> l = [1, 2, 3] # Defined list l

If we iterate over the above list..

>>> for i in l:
...     print i
... 
1
2
3

When you try to do this iteration over list l, Python for loop checks for l.__iter__() which intern return an iterator object.

>>> for i in l.__iter__():
...     print i
... 
1
2
3

To understand this more, lets customize the list and create anew list class..

>>> class ListOverride(list):
...     def __iter__(self):
...         raise TypeError('Not iterable')
... 

Here I've created ListOverride class which intern inherited from list and overrided list.__iter__ method to raise TypeError.

>>> ll = ListOverride([1, 2, 3])
>>> ll
[1, 2, 3]

And i've created anew list using ListOverride class, and since it's list object it should iterate in the same way as list does.

>>> for i in ll:
...     print i
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __iter__
TypeError: Not iterable

If we try to iterate over ListOverride object ll, we'll endup getting NotIterable exception..

gsb-eng
  • 1,211
  • 1
  • 9
  • 16