MacSanhe
MacSanhe

Reputation: 2240

Special use for __iter__ and __next__

If I want to implement this, how can I do that?

I know that every time when we call for i in object:

First go def __iter__(self) then go def __next__(self), but I don't see iter transfer any argument to next. So, how?

class MyClass(object):
    def __init__(self):
        self.data = {"a":1, "b":2, "c":3, "special":0}


for i in MyClass(): # for i in self.data, if i != "special" yield i
    print(i)

Expected Output:

a
b
c

Upvotes: 0

Views: 787

Answers (4)

miku
miku

Reputation: 188034

Another example for a custom iteration:

#!/usr/bin/env python

class Custom(object):
    def __init__(self):
        self.data = {"a":1, "b":2, "c":3, "special":0}
        self.current = 0

    def __iter__(self):
        return self

    def next(self):
        key = [k for k, v in self.data.iteritems() if v == self.current]
        if not key:
            raise StopIteration
        self.current += 1
        return key[0]

if __name__ == '__main__':
    obj = Custom()
    for i in obj:
        print(i)

Which prints the keys in data sorted by value:

'special'
'a'
'b'
'c'

As gist: https://gist.github.com/miku/01d50434b232367f8bfd#file-ex-py

Upvotes: 3

abarnert
abarnert

Reputation: 365737

The point of creating an iterator class, with __iter__ and __next__ special methods, is that it's a class, which means it can store state explicitly, in instance attributes.

For example:

class MyClass(object):
    def __init__(self, *filtered_keys):
        self.data = {"a":1, "b":2, "c":3, "special":0}
        self.filtered_keys = set(filtered_keys)
        self.iterator = iter(self.data)
    def __iter__(self):
        return self
    def __next__(self):
        while True:
            key = next(self.iterator)
            if key not in filtered_keys:
                return key

for i in MyClass("special"):
    print(i)

If you don't want to create an iterator, but just an iterable—that is, something whose __iter__ method doesn't return self, but instead returns some other object with a __next__ (and within an __iter__ that returns itself), you can return anything you want in __iter__.

def __iter__(self):
    return (key for key in self.data if key != 'special')

Or you can even make __iter__ itself a generator function, in which case it returns a generator:

def __iter__(self):
    for key in self.data:
        if key != 'special':
            yield key

Or, if you prefer:

def __iter__(self):
    yield from (key for key in self if key != 'special')

This way, the state is now inside the __iter__ method and/or the thing it returns.

Upvotes: 4

MacSanhe
MacSanhe

Reputation: 2240

I don't like my own implementation. I want make something just iterate the dict keys one time. But this method took all keys out, convert to set, and discard special and iter on the set. It's too complicate. Any better idea?

def __iter__(self):
    a = set(self.data.keys())
    a.discard("special")
    return iter(a)

Upvotes: 0

user2555451
user2555451

Reputation:

You can have the __iter__ special method filter out "special" with a generator expression:

>>> class MyClass(object):
...     def __init__(self):
...         self.data = {"a":1, "b":2, "c":3, "special":0}
...     def __iter__(self):
...         return (x for x in self.data if x != "special")
...
>>> for i in MyClass():
...     print(i)
...
a
c
b
>>>

Upvotes: 3

Related Questions