0
\$\begingroup\$

It is sometimes necessary for a generator function to expose state. In those situations it is advised to wrap the generator function as method __iter__ in a class. Here is an example:

class C:
    def __iter__(self):
        for i in range(4):
            yield i            
            self.state = i

It is convenient to use this in a for loop since the for loop will call iter() on the object, however, the object itself is not an iterator, so you cannot do this:

c = C()
print(next(c))

Which is inconvenient. To get around this inconvenience I have come up with the following class:

class C:
    def __init__(self):
        self.next = self.__iter__().__next__
    def __iter__(self):
        for i in range(4):
            yield i            
            self.state = i
    def __next__(self):
        return self.next()

Usage example:

c = C()
while 1:
    try:
        num = next(c)
        print(num)
        if num == 2:
            print("state:", c.state)
    except Exception:
        break

Is this the best way to design the class? I haven't seen this technique used anywhere else so I'm wondering why everyone else isn't using it, since the C objects can be used both as iterators and as iterables.

\$\endgroup\$
  • 5
    \$\begingroup\$ "It is sometimes necessary for a generator function to expose state" — I'd say that you should have a concrete use case to demonstrate that claim, and that the example you have posted is too sketchy or hypothetical to be on-topic for Code Review. \$\endgroup\$ – 200_success Dec 20 '18 at 4:42
3
\$\begingroup\$

Looks like a lot of extra code for very little gain, considering you can simply call iter() on an iterable object to return an iterator:

>>> c = C()
>>> i = iter(c)
>>> next(i)
0
>>> next(i)
1
>>> next(i)
2
>>> c.state
1

Also, note your new class C can have more than one iterator, with only 1 "state". The first iterator is created automatically by the constructor, for use with the next(c) call. Additional iterators are created each time you start looping over c, since iter(c) gets called and returns a new generator!

>>> c = C()
>>> next(c)
0
>>> print(next(c), c.state)
1 0
>>> print(next(c), c.state)
2 1
>>> for x in c: print(x, c.state)       # Start a new iteration
...
0 1
1 0
2 1
3 2
>>> print(next(c), c.state)             # Continue original iteration
3 2
>>> next(c)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 9, in __next__
StopIteration
\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.