Augmented generators?

Bengt Richter bokr at oz.net
Tue Jan 10 22:13:02 EST 2006


Sorry to reply this way. I saw this on google, but neither this nor my previous post
has shown up yet on my news server. Wonder what all this delay is lately ;-/

>From: Paul Rubin <http://phr...@NOSPAM.invalid>
>Subject: Re: Augmented generators?
>Date: 10 Jan 2006 11:03:39 -0800
>Message-ID: <7xwth7nbw4.fsf at ruckus.brouhaha.com>

>"Andrew Koenig" <a... at acm.org> writes:
>> Can anyone think of an easy technique for creating an object that acts like 
>> a generator but has additional methods?

>> For example, it might be nice to be able to iterate through an associative 
>> container without having to index it for each element.  

>Normally you'd define a class and give it __iter__ and next operations.
>I guess that can get messy.  Does it qualify as easy for your purposes?

>> Of course I can write such a beast as a class, but that prevents me from 
>> taking advantage of the yield statement in its implementation.

>You can make an internal function that's a generator with a yield
>statement (or a generator expression instead of a function, if simple
>enough).  The class's 'next' method would loop through the generator
>and return each value from it.

>Let me try your example:

This suffers from the same problem as my first go (an exhausted iterator
is not supposed to restart).

 >>> class kviter:
 ...    def __init__(self, d):
 ...       self.d = d
 ...    def __iter__(self):
 ...       def iter1(d, s=self):
 ...          for k in d:
 ...             # lambda not really needed here, but you wanted value to
 ...             # be callable instead of just an attribute
 ...             s.value = (lambda r=d[k]: r)
 ...             yield k
 ...       return iter1(self.d)
 ...

Using my example with your iterator:

 >>> it = kviter(dict(enumerate('abcd')))
 >>> for i in it:
 ...     if i%2: print i, it.value()
 ...
 1 b
 3 d

Ok, but this shouldn't happen at this point:

 >>> for i in it:
 ...     if i%2: print i, it.value()
 ...
 1 b
 3 d

Whereas,

 >>> class augiter(object):
 ...     def __init__(self, d):
 ...         self._it = self._gen(d)
 ...     def __iter__(self): return self
 ...     def _gen(self, d):
 ...         for k, self._v in d.items(): yield k
 ...     def next(self): return self._it.next()
 ...     def value(self): return self._v
 ...
 >>> it = augiter(dict(enumerate('abcd')))
 >>> for i in it:
 ...     if i%2: print i, it.value()
 ...
 1 b
 3 d
 >>> for i in it:
 ...     if i%2: print i, it.value()
 ...
 >>>

Letting __init__ create the generator instance by calling a bound
method coded with yield makes integrating the latter style pretty easy,
even though you still need __iter__ and next methods.

Hm, you could factor that out into a base class though:
(then you just need the convention that a derived class must define at least
the _gen method. Then augment to taste)

 >>> class AugiterBase(object):
 ...     def __init__(self, *args, **kw):
 ...         self._it = self._gen(*args, **kw)
 ...     def __iter__(self): return self
 ...     def next(self): return self._it.next()
 ...
 >>> class DervAugit(AugiterBase):
 ...     def _gen(self, d):
 ...         for k, self._v in d.items(): yield k
 ...     def value(self): return self._v
 ...
 >>> it = DervAugit(dict(enumerate('abcd')))
 >>> for i in it:
 ...     if i%2: print i, it.value()
 ...
 1 b
 3 d
 >>> for i in it:
 ...     print i, it.value()
 ...
 >>> it = DervAugit(dict(enumerate('abcd')))
 >>> for i in it:
 ...     print i, it.value()
 ...
 0 a
 1 b
 2 c
 3 d

Regards,
Bengt Richter



More information about the Python-list mailing list