Reputation: 1280
Considering the following iterator:
class MyIterator:
def __iter__(self):
print("Calling `__iter__`")
return self
def __reversed__(self):
print("Calling `__reversed__`")
return self
def __next__(self):
print("Calling `__next__`")
raise StopIteration
Is there a for
-based equivalent syntax of the following code:
R = reversed(MyIterator())
while True:
try:
next(R)
except StopIteration:
break
which actually prints
Calling `__reversed__`
Calling `__next__`
for
syntax seems to always add a call to iter
, even though the given object already respects the Iterator protocol.
Upvotes: 0
Views: 38
Reputation: 155506
Calling __iter__
implicitly is intentional; for
doesn't know if it's been handed an iterator or an iterable, and needs to unconditionally ensure it has an iterator, which it does by invoking __iter__
. If your object is already an iterator, __iter__
is supposed to be idempotent, doing nothing but returning itself (the body should be nothing but return self
). There are some tests in existing Python code that detect iterators by testing iter(x) is x
, and if you violate the rules by doing something other than returning self
in your custom iterator, you're responsible for the misbehavior.
Point is, what you want to do makes no sense. Either write an iterable (without a __next__
, and a __iter__
that returns a new iterator) or write an iterator, but if you write an iterator, you need to obey the rules for iterators.
Just to be clear, the unavoidable order of operations here is:
reversed
invokes __reversed__
, which should in theory return a new reversed iterator (it's very weird to have it return the existing, theoretically forward iterator)for
invokes __iter__
implicitly on whatever reversed
returns to ensure it's an iterator (if __reversed__
were implemented properly, it would have returned a brand new iterator, but instead, you end up invoking __iter__
on the same object)for
then call __next__
implicitly for each item until StopIteration
is raisedTo fix it, have __reversed__
return a new reverse iterator, not self
; the new iterator's __iter__
will still be invoked, but it won't be the one on the original iterator. Even better, split your implementations; something that's reversible shouldn't be an iterator in the first place. Just make __iter__
and __reversed__
both generator functions that produce whatever they should produce, and get rid of __next__
, to make your class iterable and reversible, but not an iterator.
Upvotes: 3