abcamiletto
abcamiletto

Reputation: 84

Uncommenting `if False: yield` changes `__iter__` behaviour

I cannot figure why the behavior of __iter__ changes when I uncomment if False: yield. The condition is never true, so why does the result change?

class Example:
    def __init__(self):
        self.lst = [1,2,3]
        self.size = 3

    def __iter__(self):
        self.n = 0
        #if False: yield 
        return self

    def __next__(self):
        self.n += 1
        if self.n > self.size:
            raise StopIteration
        return self.lst[self.n-1]

ex = Example()
for i in ex:
    print(i) 

In this code everything works as expected, and it prints out the list.

If I uncomment the the if False: yield in the __iter__ method then the iterator stops working and nothing gets printed out, even if that line never gets executed.

Upvotes: 4

Views: 129

Answers (2)

Robin De Schepper
Robin De Schepper

Reputation: 6365

When you uncomment your line if False: yield the Python interpreter will compile the def __iter__(self) into a generator rather than a function. A generator is an iterator encapsulated in a function that will keep returning values (on next calls) for as long as yield statements keep providing values and will raise a StopIteration when the generator returns out of its function:

>>> class A:
...  def __iter__(self):
...    if False:
...      yield
...
>>> a = A()
>>> type(iter(a))
<class 'generator'>
>>> class B:
...   def __iter__(self):
...     return self
...   def __next__(self):
...     return 1
...
>>> type(iter(B()))
<class '__main__.B'>

When you comment the line your __iter__ is a regular function and returns self as an iterator, any class that implements a __next__ method can be used as an iterator; if you hadn't you'd get this error:

>>> class B:
...   def __iter__(self):
...     return self
...
>>> type(iter(B()))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: iter() returned non-iterator of type 'B'

Note that if all __iter__ does is return self that you don't even need to implement it, just __next__ would suffice. Your __iter__ implementation is even dangerous: Multiple iter(ex) calls will all return the same ex object as iterator and will reset the n of all these "iterators" actually using the same ex:

>>> ex = Example()
>>> itr1 = iter(ex)
>>> print(next(itr1), next(itr1))
1 2
>>> iter(ex)
>>> print(next(itr1))
1

Calling iter again resets your previous iterator reference? I can't imagine that's what you are trying to achieve ;)

Upvotes: 3

maney
maney

Reputation: 76

The if loop never executes if the condition is False. So, when you use if False: since the condition is False it never executes. Also, you don't have anything specified in yield.

This can be rewritten as,

class Example:
    def __init__(self):
        self.lst = [1,2,3]
        self.size = 3
        self.n = 0

    def __iter__(self):
        self.n = 0
        for i in self.lst:
            yield i

    def __next__(self):
        self.n += 1
        if self.n > self.size:
            raise StopIteration
        return self.lst[self.n-1]

ex = Example()
for i in ex:
    print(i)

The output will be as desired.

Upvotes: -1

Related Questions