Reputation: 84
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
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
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