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.

+3


source to share


1 answer


Python 2.7

To be an iterator, must implement the iterator protocol:

  • defines obj.__iter__

    ANDobj.next

  • obj.__iter__

    should return self

  • After has StopIteration

    been raised, subsequent calls obj.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.

+3


source







All Articles