[Tutor] How to use __iter__

Peter Otten __peter__ at web.de
Tue Aug 9 08:56:37 CEST 2011


Jia-Yu Aw wrote:

> How to use __iter__
> Von:
> Jia-Yu Aw <jiayuaw at yahoo.com.sg>
>   Antwortadresse:
> Jia-Yu Aw <jiayuaw at yahoo.com.sg>
>   Datum:
> Dienstag, 9. August 2011 03:48:20
>   An:
> tutor at python.org <tutor at python.org>
>   Gruppen:
> gmane.comp.python.tutor
> Hi all
> 
> I've been learning Classes and have read the documentation on __iter__ but 
still don't understand how it works. I have two pieces of code: the first is 
def __iter__, the second is using __iter__. Instead of being an iterator, 
I'm just using this is a function, which I think is incorrect. Can somebody 
explain __iter__ and, if it's related, next()?

An "iterable" is an object that has an with an __iter__() method. The 
__iter__() method is supposed to return an "iterator", i. e. an object with 
a next() method. The next() method is then called repeatedly until a next() 
call raises a StopIteration. You can think of

for item in items:
    do_something_with(item)

as syntactic sugar for

it = items.__iter__()
while True:
    try:
        item = it.next()
    except StopIteration:
        break
    do_something_with(item)

With that in mind 
> This is the first. But unlike what it says in the triple quotes, I return 
a whole list instead of a single shape:
> 
>     def __iter__(self):
>         """
>         Return an iterator that allows you to iterate over the set of
>         shapes, one shape at a time
>         """
>         import copy
>         setCopy = copy.copy(self.set)
>         shapeList = []
>         while len(setCopy) > 0:
>             try:
>                 y = setCopy[0]
>                 shapeList.append(y)
>                 setCopy.remove(y)
>             except StopIteration:
>                 break
>         return shapeList

your __iter__() method would become

class MyIter(object):
    def __init__(self, items):
        self.items = list(items)
    def next(self):
        if len(self.items) == 0:
            raise StopIteration
        return self.items.pop(0)

class MyIterable(object):
    # ...
    def __iter__(self):
        return MyIter(self.set)

However, this is a clumsy way to implement an iterable.
One alternative takes advantage of the fact that Python already knows how to 
iterate over a set. 

class MyIterable(object):
    def __iter__(self):
        for item in self.set:
            yield item
        
Here the "yield" statement turns __iter__() from a function into a 
"generator". Every call of a generator creates a new iterator, i. e. an 
object with a next() method. Every next() call proceeds execution of the 
generator's body until it reaches a yield. Once the end of the body is 
reached next() will raise Stopiterations forever:

>>> def f():
...     yield 1
...     yield 2
...
>>> g = f()
>>> g.next()
1
>>> g.next()
2
>>> g.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> g.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

A final simplification would be

class MyIterable(object):
    def __iter__(self):
        return iter(self.set)
 
> The second, how I've used __iter__:
> 
> def findLargest(shapes):
>     """
>     Returns a tuple containing the elements of ShapeSet with the
>        largest area.
>     shapes: ShapeSet
>     """
>     ## TO DO
>     
>     areaList = []
>     areaDict = {}
>     largestDict = {}

Don't do that:
     
>     shapeList = shapes.__iter__()
>     for s in shapeList:

The for-loop implicitly invokes the __iter__() method. Just loop over the 
original object:

      for s in shapes:
>         areaDict[s] = s.area()
>         areaList.append(s.area())
>         
>     largestArea = max(areaList)
>     areaList = [] #re-initialize areaList, cleaning it
> 
>     import copy
>     dictCopy = copy.copy(areaDict)
>     for a in areaDict:
>         if areaDict[a] != largestArea:
>             del dictCopy[a]
> 
>     return tuple(dictCopy)

Side note: you are copying data way to much. Don't! Most occurences of 
copy() only serve to help an experienced Pythonista identify newbie code ;)

As an inspiration here's an alternative implementation:

def find_largest(shapes):
    largest_area = max(shape.area() for shape in shapes)
    return [shape for shape in shapes if shape.area() == largest_area]





More information about the Tutor mailing list