[Tutor] lazy? vs not lazy? and yielding

spir denis.spir at gmail.com
Thu Mar 4 10:05:26 CET 2010


On Wed, 03 Mar 2010 15:41:34 -0500
Dave Angel <davea at ieee.org> wrote:

> John wrote:
> > Hi,
> >
> > I just read a few pages of tutorial on list comprehenion and generator 
> > expression.  From what I gather the difference is "[ ]" and "( )" at the 
> > ends, better memory usage and the something the tutorial labeled as "lazy 
> > evaluation".  So a generator 'yields'.  But what is it yielding too?  
> >
> > John
> >
> >   
> A list comprehension builds a whole list at one time.  So if the list 
> needed is large enough in size, it'll never finish, and besides, you'll 
> run out of memory and crash.  A generator expression builds a function 
> instead which *acts* like a list, but actually doesn't build the values 
> till you ask for them.

To append on Dave's explanation, let us a take special case: when the sequence is potentially infinite. Imagine you want a function (actually here a generator) to yield the sequence of squares of natural without any limit. The stop point will happen in the code using the generator. You could write it eg like that (this is just sample code):

def squares():
    squares.n = min if min
	while True:
        squares.n += 1
        n = squares.n
        yield n*n
squares.n = 0

Then, you can use it for instance to get the first 9 squares that happen to be multiples of 3.

nineTripleSquares = []
for sq in squares():
    if sq%3 == 0:
        nineTripleSquares.append(sq)
        if len(nineTripleSquares) == 9:
            break
print nineTripleSquares
# ==> [9, 36, 81, 144, 225, 324, 441, 576, 729]

A list can hardly be used here, instead of a generator, because we do not know how long the list should be so that we get 9 final items.
We can modify squares to give it borders:

def squares(min=None, max=None):
    squares.n = min-1 if (min is not None) else 0
    squares.max = max
    while True:
        squares.n += 1
        n = squares.n
        if (squares.max is not None) and (n > squares.max):
            raise StopIteration
        yield n*n
squares.n = 0

tripleSquares = []
for sq in squares(7,27):
    if sq%3 == 0:
        tripleSquares.append(sq)
print tripleSquares
# [81, 144, 225, 324, 441, 576, 729]

A more object-oriented vaersion would look like:

class Squares(object):
    def __init__(self, min, max):
        self.n = min-1 if (min is not None) else 0
        self.max = max
    def next(self):
        self.n += 1
        n = self.n
        if (self.max is not None) and (n > self.max):
            raise StopIteration
        return n*n
    def __iter__(self):
        return self

tripleSquares = []
for sq in Squares(7,27):
    if sq%3 == 0:
        tripleSquares.append(sq)
print tripleSquares
# ==> same result

This defines a new type of "iterable" objects. When you use "for item in obj" on such object, python looks for the magic method __iter__ to find its iterator: here, itself. Then it will repetedly call the iterator's next method to get each item.


Denis
-- 
________________________________

la vita e estrany

spir.wikidot.com



More information about the Tutor mailing list