Generators In Python

last modified: April 25, 2006

Generators are cool. Now we have them in Python 2.2 and later.

See also http://python.org/cgi-bin/moinmoin/Generators

Alternative Viewpoint:

You don't need generators if you have proper lexical closures/first class functions/lambdas. You don't even need CallWithCurrentContinuation to implement CoRoutines (which is what generators are). Generators add unnecessary complexity that could simply be achieved by enhancing existing features in the PythonLanguage.

Agreed. I think it's funny that Python code using "yield" looks so remarkably similar to equivalent Ruby code using "yield", but uses a completely different underlying mechanism.

GeneratorsAreNotCoroutines. See the following excellent post by TimPeters, which explains the difference between generators, coroutines and continuations. http://mail.python.org/pipermail/python-dev/1999-July/000467.html


Some examples of using generators...


Probably the most frequent use of generators would be in lexing/parsing.

"""
Generator Example for Python 2.2 by JuneKim
refer to McMillan's coroutine example at
http://davidf.sjsoft.com/mirrors/mcmillan-inc/tutorial4.html
and PEP-255

    The result should be:

    d = sqrt(b^2 - 4*a*c); twoa = 2*a;
    L = -b/twoa; R = d/twoa; A1 = L + R;
    A2 = L - R                                                             
done
"""

test = """
           d    =   sqrt(b**2  -  4*a*c)
        twoa    =   2*a
           L    =   -b/twoa
           R    =   d/twoa
          A1    =   L + R
          A2    =   L - R\0
        """
def getline(text):
    for line in text.split('\n'):
        yield line

def disassembler(text):
    getlineGen=getline(text)
    for eachline in getlineGen:
        for c in eachline:
            if c=='\0':
                yield c
                return #raise StopIteration(shouldn't reach)
            yield c
        yield ';'

def squasher(text):
    disGen=disassembler(text)
    for c in disGen:
        if c=='*':
            c2=disGen.next()
            if c2=='*':
                c='^'
            else:
                yield c
                c=c2
        if c in ' \t':
            for c2 in disGen:
                if c2 not in ' \t':
                    break
            yield ' '
            c=c2
        if c=='\0':
            yield c
            return #raise StopIteration(shouldn't reach)
        yield c

def assembler(text):
    squasherGen=squasher(text)
    line=''
    for c in squasherGen:
        if c=='\0':
            break
        if len(line)==72:
            yield line
            line=''
        line+=c
    line+=' '*(72-len(line))
    yield line

def putline(text):
    assemblerGen=assembler(text)
    for eachline in assemblerGen:
        print eachline

if __name__=='__main__':
    putline(test)
    print 'done'

-- JuneKim


You can emulate the unix-style shell piping with generators.

from __future__ import generators

class GenPipe:
    def __init__(self, generator):
        self.gen = generator #sanity check omitted
    def __or__(self,nextPipe):
        self.gen=nextPipe._run(self.gen)
        return self
    def __iter__(self):
        return self.gen

class PipeFilter:
    def __init__(self):
        self._followingPipes=[]
    def __or__(self,nextPipe):
        self._followingPipes+=(nextPipe,)
        return self
    def _run(self,gen):
        gen=self.run(gen)
        while self._followingPipes:
            gen=self._followingPipes[0].run(gen)
            self._followingPipes.pop(0)
        return gen
    def run(self,gen):
        raise NotImplementedError

class Test(PipeFilter):
    def __init__(self,test):
        PipeFilter.__init__(self)
        self.test=test
    def run(self,gen):
        for x in gen:
            if self.test(x):
                yield x

class Quit(PipeFilter):
    def __init__(self,test):
        PipeFilter.__init__(self)
        self.test=test
    def run(self,gen):
        for x in gen:
            if self.test(x): 
                raise StopIteration
            else:
                yield x

class Count(PipeFilter):
    def __init__(self,n):
        PipeFilter.__init__(self)
        self.n=n
    def run(self,gen):
        for x in gen:
            yield x
            self.n -= 1
            if self.n == 0 :
                break 

def Ints():
    n = 0
    while 1:
        yield n
        n += 1

def Files( start ):
    import os
    for file in os.listdir( start ):
        file = os.path.join( start, file )
        if os.path.isfile( file ): yield file
        elif os.path.isdir(file):
            for more in Files( file ):
                yield more

>>> odd = Test(lambda x:x % 2)
>>> notDivBy3 = Test(lambda x: x % 3 )
>>> oddNotDivBy3 = odd|notDivBy3
>>> firstTen= Count(10)
>>> result=GenPipe(Ints())|firstTen|oddNotDivBy3
>>> for each in result:
>>>     print each

>>> oddLowerThanTen=odd|Quit(lambda y:y>=10)
>>> p3=GenPipe(Ints())|oddLowerThanTen|notdiv3

>>> isPyFile=Test(lambda s:s.split('.')[-1].lower()=='py')
>>> tenPyFiles=GenPipe(Files('/'))|isPyFile|Count(10)
>>> for each in tenPyFiles:
>>>     print each

-- JuneKim


Take a look at this site which shows how to use generators to load data from a database: http://www.halfcooked.com/mt/archives/000497.html


CategoryPython


Loading...