[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