Calling next() on that iterator enters the function and runs it until a yield statement is encountered. The value that is yielded (or None in the case of a bare "yield" statement) becomes the return value for next(), and execution of the function pauses after the call to yield. The next time that next() is called, execution resumes inside the function where it left off during the previous call to next(). If you've worked with continuations, you will likely notice parallels between continuations and generators.
If a call to next() ever falls off the end of the function, StopIteration is raised. The required __iter__ method is also defined automatically.
Generators offer us a simple technique for implementing an iterator since they handle the details of the iterator protocol for us.
def counter (start=0, stop=None, by=1): if stop is None: while True: yield start start += by else: while start < stop: yield start start += by
cntr = counter(0, 10) cntr.next() # 0 cntr.next() # 1 for n in cntr: print n, # prints 2 3 4 5 6 7 8 9