[Tutor] Iterator vs. iterable cheatsheet, was Re: iter class

spir denis.spir at gmail.com
Fri Jan 24 14:50:15 CET 2014


On 01/24/2014 10:22 AM, Peter Otten wrote:
>
> There's an odd outlier that I probably shouldn't tell you about [...]

I guess there is a whole class of outliers; not really sure how to classify 
them. This is the case of defining a wrapper or "proxy" type, for a underlying 
data structure which is iterable, typically a builtin collection. This was 
evoked (but not specifically termed as "wrapper" or such) in previous message of 
the orginal thread. In that case, __iter__ would neither return self (it is not 
an iterator), nore a hand-baked iterator, but the builtin (or already defined) 
one of the underlying iterable. Two example, rather artificial (but code works):

First, say you have an array of angles, which for the user are measured in 
degrees but internally use radians. (A better example may be of internal 
machine-friendly RGB colors and external user-friendly HSL colors [not HSV! 
grrrr...].) At the interface, there is conversion. Iteration actually is 
iterating on the internal array, thus just uses iter().

import math ; TAU = 2 * math.pi
class Angles:
     def __init__ (self):
         self.angles = list()
     def put (self, angle):
         self.angles.append(angle * TAU / 360)
     def __getitem__ (self, i):
         return self.angles[i] * 360 / TAU
     def __iter__ (self):
         return iter(self.angles)

angles = Angles()
angles.put(100) ; angles.put(200) ; angles.put(300)
print(angles[1])
for a in angles: print(a)

Second, we build an associative array (for few entries) as a plain association 
list à la Lisp, but implemented as a pair of lists instead as a list of pairs 
(this saves an intermediate notion of Pair). Iterating is here on the pair of 
list, zipped (in the python sense) together:

class AssList:
     def __init__ (self):
         self.keys, self.vals = list(), list()
     def put (self, key, val):
         self.keys.append(key)
         self.vals.append(val)
     def __getitem__ (self, i):
         return self.keys[i], self.vals[i]
     def __iter__ (self):
         return iter(zip(self.keys, self.vals))

al = AssList()
al.put(1,'a') ; al.put(2,'b') ; al.put(3,'c')
print(al[1])
for k,v in al: print(k,v)

Side question: what is the point of __iter__ on iterators? Take a 'for' loop like:
	for x in xs: f(x)
In the case where xs is not an iterator (no __next__), python calls iter(xs), 
which IIUC may call xs.__iter__() unless it is a builtin. But if xs is an 
iterator (__next__ is there), then Python uses it directly, thus what is the 
point of __iter__ there? In any case, python must check whether xs is an 
iterator (__next__). So there is no sense in calling __iter__ on an iterator. 
Logically, this would lead to an infinite recursion (calling __iter__ again and 
again). But python does it anyway (and stops after the first call, indeed):

class Iter:
     def __init__ (self): self.n = 0
     def __next__ (self):
         if self.n == 10: raise StopIteration
         self.n += 1
         return self.n
     def __iter__ (self):
         print("*** __iter__ ***")		# *********
         return self
it = Iter()
for i in it: print(i, end=" ")
print()

huh?

The only theoretical case I can find is iterators which do implement the 
protocol (__next__) but are not to be used (!), instead delegate to another 
iterator. Then, why do they bear __next__ at all? why are they iterators at all?

denis


More information about the Tutor mailing list