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