Confused about the exact work of the following () and __iter __ ()
While experimenting with the iterator implementation, I got confused about next (). I made a simple test script where the iterator works as I expected:
class Object:
def __init__(self, name):
self.name = name
def prin(self):
print self.name
class Some:
def __init__(self):
self.data = list()
def __iter__(self):
return self.SomeIter(self, len(self.data))
def fill(self, obj):
self.data.append(obj)
def printMe(self):
for entry in self:
print entry.name
class SomeIter:
def __init__(self, some, end):
self.index = 0
self.end = end
self.name = some.data[self.index].name
self.data = some.data
def next(self):
if self.index == self.end:
raise StopIteration
else:
self.name = self.data[self.index].name
self.index += 1
return self
########################################################################
someX = Some()
obj1 = Object("A")
obj2 = Object("B")
someX.fill(obj1)
someX.fill(obj2)
someX.fill(obj2)
for obj in someX:
print obj.name
I am getting "AB B" as output. It's all good. But then I also have an iterator for the tree class. The next () method works basically the same. First, I update the instance and return it. But in the case of an iterator tree, the first element is skipped. This makes sense to me as I only go back to myself after updating the instance. But why can I get different behavior in the case of the above implementation, where the instance is updated and only then returned?
########################################################################
# RIGHT-HAND-CORNER-BOTTOM-UP-POST-ORDER-TRAVERSAL-ITERATOR
########################################################################
class RBPIter:
"""!
@brief Right hand corner initialised iterator, traverses tree bottom
up, right to left
"""
def __init__(self, tree):
self.current = tree.get_leaves(tree.root)[-1] # last leaf is right corner
self.csi = len(self.current.sucs)-1 # right most index of sucs
self.visited = list() # visisted nodes
self.tree = tree
self.label = self.current.label
########################################################################
def __iter__(self):
return self
########################################################################
def begin(self):
return self.tree.get_leaves(self.tree.root)[-1]
########################################################################
def end(self):
return self.tree.root
########################################################################
def find_unvisited(self, node):
"""!
@brief finds rightmost unvisited node transitively dominated by node
"""
leaves = self.tree.get_leaves(self.tree.root)
# loop through leaves from right to left, as leaves are listed
# in order, thus rightmost list elememt is rightmost leaf
for i in range(len(leaves)-1, -1, -1):
# return the first leaf, that has not been visited yet
if leaves[i] not in self.visited:
self.label = leaves[i].label
return leaves[i]
# return None if all leaves have been visited
return None
########################################################################
def go_up(self, node):
"""!
@brief sets self.current to pred of self.current,
appends current node to visited nodes, reassignes csi
"""
self.visited.append(self.current)
self.current = self.current.pred
if self.current.sucs[0] not in self.visited:
self.current = self.find_unvisited(self.current)
self.label = self.current.label
self.csi = len(self.current.sucs)-1
self.visited.append(self.current)
########################################################################
def next(self):
"""!
@brief advances iterator
"""
# if current node is a leaf, go to its predecessor
if self.current.suc_count == 0 or self.current in self.visited:
self.go_up(self.current)
# if current node is not a leaf, find the next unvisited
else:
self.current = self.find_unvisited(self.current)
if self.current == self.end():
raise StopIteration
return self
Edit 1:
I was comparing the outputs of both iterators and they are different, SomeIter outputs the first element 2 times:
next: A
A
next: A
B
next: B
B
next: B
the other iterator fails:
next: a
s
next: s
i
next: i
r
next: r
t
next: t
t
next: t
s
next: s
e
next: e
t
next: t
otherwise "next: a" will happen 2 times
Edit 2:
It doesn't really make any feeling for me ...
see these calls and output:
someXIter = iter(someX)
print someXIter.next().name
print someXIter.next().name
print someXIter.next().name
output:
next: A
A
next: A
B
next: B
B
with this code:
class SomeIter:
def __init__(self, some, end):
self.index = 0
self.end = end
self.name = some.data[self.index].name
self.data = some.data
def next(self):
print "next: ", self.name
if self.index == self.end:
raise StopIteration
else:
self.name = self.data[self.index].name
self.index += 1
return self
Why doesn't it make any sense to me? Since, since next () is called the first time, it prints "next: A", then the instance is updated and the return value of the function call is returned, which is "A" again. Wah? Why is it not "B" as the return value must be an updated instance.
source to share
Python 2.7
To be an iterator, must implement the iterator protocol:
- defines
obj.__iter__
ANDobj.next
-
obj.__iter__
should returnself
- After has
StopIteration
been raised, subsequent callsobj.next()
(next(obj)
) should raiseStopIteration
If the class defines only __iter__
, __iter__
should return an object that implements the iterator protocol. If the members of the class are contained in a built-in type such as a list, it __iter___
can simply return iter(list)
.
I guess what is implied in the whole concept is that the iterator should keep track of where it is in the iteration.
If you want your object to be an iterator with different iteration sequences, you can define different methods to track the iteration and move to the next element, and then use those methods in obj.next()
.
A trivial example:
class Thing(object):
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
def __repr__(self):
return 'Thing({})'.format(self.name)
class Some(object):
def __init__(self):
self.data = None
# need something to keep track of the iteration sequence
self.__index = None
# type of iteration do you want to do
self.direction = self.forward
def __iter__(self):
# reset the iteration
self.__index = None
return self
def next(self):
try:
return self.direction()
except IndexError:
raise StopIteration
def forward(self):
if self.__index is None:
self.__index = -1
self.__index += 1
return self.data[self.__index]
def reverse(self):
if self.__index is None:
self.__index = 0
self.__index -= 1
return self.data[self.__index]
Using
>>> some = Some()
>>> some.data = [Thing('A'),Thing('B'),Thing('C'),Thing('D')]
>>> for thing in some:
print thing,
A B C D
>>> some.direction = some.reverse
>>> for thing in some:
print thing,
D C B A
>>>
So just simple next
simple and put your tree traversal logic in different methods. This can make it easier to test this logic. And you can always add different types of behavior:
def odd_then_even(self):
if self.__index is None:
self.__index = -1
self.__odd = True
self.__index += 2
try:
return self.data[self.__index]
except IndexError:
if not self.__odd:
raise IndexError
self.__odd = False
self.__index = 0
return self.data[self.__index]
>>> some.direction = some.odd_then_even
>>> for thing in some:
print thing,
B D A C
>>>
I'm having a hard time figuring out how your inner class solution would work, but one thing that doesn't look good is that the method of next
your iterator objects returns itself and it seems like it next
should return the next item in the sequence / collection. When you iterate over a lot of things, the iteration should serve the individual things, not a modified instance of the iterator object.
source to share