i=2; lst=[i**=2 while i<1000]

Michael Spencer mahs at telcopartners.com
Tue Dec 6 14:32:57 EST 2005


Daniel Schüle wrote:
> Hello NG,
> 
> I am wondering if there were proposals or previous disscussions in this 
> NG considering using 'while' in comprehension lists
> 
> # pseudo code
> i=2
> lst=[i**=2 while i<1000]
> 

You are actually describing two features that list comps don't natively support 
- while-based termination, and calculating based on prior values of output.  Of 
course there are work-arounds for both, which others have shown.  Here's another 
  approach:

The while-based termination can be easily achieved using itertools.takewhile, e.g.,:

  >>> list(itertools.takewhile(lambda x: x < 10, range(100)))
  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  >>>

the harder piece is to access the prior value.  One way is like this:

def chasetail(start, func):
     from itertools import tee
     def mygen():
         yield start
         for i in (func(i) for i in iterators[0]):
             yield i
     iterators = tee(mygen())
     return iterators[1]

the trick is to create two independent iterators, using itertools.tee, one of 
which is consumed internally in the func(i) for i in iterators[0] generator 
expression, the other is returned to use code.

  >>> it = chasetail(2, lambda x: x*x) #careful - this won't terminate
  >>> it.next()
  2
  >>> it.next()
  4
  >>> it.next()
  16
  >>> it.next()
  256
  >>> it.next()
  65536
  >>>

Then you can combine these two approaches to get something semantically like 
what you wanted in the first place (although not as pretty ;-)

  >>> list(itertools.takewhile(lambda x: x < 1000, chasetail(2, lambda x: x*x)))
  [2, 4, 16, 256]
  >>>



If you like this sort of thing, you might want to generalize the concept with a 
Stream class.  Here's minimal implementation:

import itertools as it

class Stream(object):
     """An extendable stream, that provides a separate iterator
     (using itertools.tee) on every iteration request"""

     def __init__(self, *iterables):
         self.queue = list(iterables)
         self.itertee = it.tee(self._chain(self.queue))[0]

     def _chain(self, queue):
         while queue:
             for i in self.queue.pop(0):
                 self.head = i
                 yield i

     def extend(self,other):
         self.queue.append(other)

     def __iter__(self):
         """Normal iteration over the iterables in self.queue in turn"""
         return self.itertee.__copy__()


then, you can write your squaring algorithm as:

  >>> s= Stream([2])
  >>> s.extend(it.takewhile(lambda x: x < 1000, (i**2 for i in s)))
  >>> list(s)
  [2, 4, 16, 256]


Michael







More information about the Python-list mailing list