zerocool
zerocool

Reputation: 3522

__iter__ (iterator and generator)

I read a book about advanced topics in python. The author was trying to explain the generator.

This was his example to explain:

class rev:
    def __init__(self,data):
        self.data = data
        self.index  = len(data)
    def __iter__(self):
        return self
    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

def main():
    reve = rev('zix')
    for i in reve:
        print(i)

if __name__=='__main__':
    main()

The main idea of this code is to reverse generators. The output is :

x
i
z

The thing I found hard to understand is this part:

def __iter__(self):
    return self

Can someone explain it to me?

Upvotes: 1

Views: 1386

Answers (3)

Ethan Furman
Ethan Furman

Reputation: 69298

The iterator protocol is comprised of two methods:

  • __iter__, and
  • __next__

Also, a requirement is that __iter__ returns self -- so if you have an obj that is an iterator then

obj is iter(obj) is obj.__iter__()

is true.

This is a good thing because it allows us to say iter = iter(obj) and if obj was already an iterator we still have the same object.

Upvotes: 1

C Panda
C Panda

Reputation: 3415

When you do for x in xs, xs has to be an iterable, which means you can get an iterator out of it by iter(xs), which you can do when xs.__iter__() is implemented. An iterator is required to implement __next__(), so that the in operator can consume it one by one by calling next() on it.

Now, in your case

reve = rev("hello") # is an iterable, as there is rev.__iter__()
rev1 = iter(reve)   # is an iterator, this is what rev.__iter__() returns
rev2 = next(rev1)   # now go on calling next() till you get StopIteration

Type the above snippet in REPL. Run it a few times. You will get a feel for it.

Upvotes: 2

fips
fips

Reputation: 4399

Since your class provides an implementation for next() it returns self so the caller will call that when looping over your object. In contrast, if you only wanted to wrap a data structure that already provides an implementation of __iter__ and next (e.g. list), you could return self.data.__iter__() from your class. In that case the caller would call next() defined on that object when doing a loop or list comprehension let's say.

class rev:
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def next(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]


class rev2:
    def __init__(self, data):
        self.data = data

    def __iter__(self):
        return self.data.__iter__()


def main():
    for i in rev('zix'):  # str doesn't provide implementation for __iter__
        print(i)
    for i in rev2(['z', 'i', 'x']):  # list does so no need to implement next
        print(i)

Upvotes: 0

Related Questions