[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