views:

797

answers:

11

Reading the Python cookbook at the minute and currently looking at generators. I'm finding it hard to get my head round.

As I come from a Java background, is there a Java equivelant? The book was speaking about 'Producer / Consumer', however when I hear that I think of threading.

Can anyone explain what a generator is and why you would use it? Without quoting any books, obviously(Unless you can find a decent, simplistic answer direct from a book). Perhaps with examples, if you're feeling generous! :) Much appreciated.

+8  A: 

Generators could be thought of as shorthand for creating an iterator. They behave like a Java Iterator. Example:

>>> g = (x for x in range(10))
>>> g
<generator object <genexpr> at 0x7fac1c1e6aa0>
>>> g.next()
0
>>> g.next()
1
>>> g.next()
2
>>> list(g)   # force iterating the rest
[3, 4, 5, 6, 7, 8, 9]
>>> g.next()  # iterator is at the end; calling next again will throw
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Hope this helps/is what you are looking for.

Update:

As many other answers are showing, there are different ways to create a generator. You can use the parentheses syntax as in my example above, or you can use yield. Another interesting feature is that generators can be "infinite" -- iterators that don't stop:

>>> def infinite_gen():
...     n = 0
...     while True:
...         yield n
...         n = n + 1
... 
>>> g = infinite_gen()
>>> g.next()
0
>>> g.next()
1
>>> g.next()
2
>>> g.next()
3
...
overthink
+31  A: 
Stephan202
Won't this for 2.6 as well?
Geo
The difference is next(g) vs g.next()
tgray
@nikow: valid point. I rephrased that sentence.
Stephan202
Very thorough answer. I really appreciate it :)
day_trader
You mention it is possible to `send` data to a generator. Once you do that you have a 'coroutine'. It's very simple to implement patterns like the mentioned Consumer/Producer with coroutines because they have no need for `Lock`s and therefore can't deadlock. It's hard to describe coroutines without bashing threads, so I'll just say coroutines are a very elegant alternative to threading.
THC4k
Your examples work with python 2.6 (both g.next() and next(g) produce the same result), a really useful post Thanks!
Chris Huang-Leaver
+5  A: 

There is no Java equivalent.

Here is a bit of a contrived example:

#! /usr/bin/python
def  mygen(n):
    x = 0
    while x < n:
     x = x + 1
     if x % 3 == 0:
      yield x

for a in mygen(100):
    print a

There is a loop in the generator that runs from 0 to n, and if the loop variable is a multiple of 3, it yields the variable.

During each iteration of the for loop the generator is executed. If it is the first time the generator executes, it starts at the beginning, otherwise it continues from the previous time it yielded

iWerner
The last paragraph is very important: The state of the generator function is 'frozen' everytime it yields sth, and continues in exactly the same state when it is invoked the next time.
jellybean
There's no syntactic equivalent in Java to a "generator expression", but generators -- once you've got one -- are essentially just an iterator (same basic characteristics as a Java iterator).
overthink
@overthink: Well, generators can have other side effects that Java iterators can't have. If I were to put `print "hello"` after the `x=x+1` in my example, "hello" would be printed 100 times, while the body of the for loop would still only be executed 33 times.
iWerner
@iWerner: Pretty sure the same effect could be had in Java. The implementation of next() in the equivalent Java iterator would still have to search from 0 to 99 (using your mygen(100) example), so you could System.out.println() each time if you wanted. You'd only return 33 times from next() though. What Java lacks is the very handy yield syntax which is significantly easier to read (and write).
overthink
+2  A: 

It helps to make a clear distinction between the function foo, and the generator foo(n):

def foo(n):
    yield n
    yield n+1

foo is a function. foo(6) is a generator object.

The typical way to use a generator object is in a loop:

for n in foo(6):
    print(n)

The loop prints

# 6
# 7

Think of a generator as a resumable function.

yield behaves like return in the sense that values that are yielded get "returned" by the generator. Unlike return, however, the next time the generator gets asked for a value, the generator's function, foo, resumes where it left off -- after the last yield statement -- and continues to run until it hits another yield statement.

Behind the scenes, when you call bar=foo(6) the generator object bar is defined for you to have a next attribute.

You can call it yourself to retrieve values yielded from foo:

next(bar)    # works in python2.6 or python3.x
bar.next()   # works in python2.5+, but is deprecated. Use next() if possible.

When foo ends (and there are no more yielded values), calling next(bar) throws a StopInteration error.

unutbu
+4  A: 

A generator is effectively a function that returns (data) before it is finished, but it pauses at that point, and you can resume the function at that point.

>>> def myGenerator():
...     yield 'These'
...     yield 'words'
...     yield 'come'
...     yield 'one'
...     yield 'at'
...     yield 'a'
...     yield 'time'

>>> myGeneratorInstance = myGen()
>>> next(myGeneratorInstance)
These
>>> next(myGeneratorInstance)
words

and so on. The (or one) benefit of generators is that because they deal with data one piece at a time, you can deal with large amounts of data; with lists, RAM could become a problem. Generators, just like lists, are iterable, so they can be used in the same ways:

>>> for word in myGeneratorInstance:
...     print word
These
words
come
one
at 
a 
time

Note that generators provide another way to deal with infinity, for example

>>> from time import gmtime, strftime
>>> def myGen():
...     while True:
...         yield strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())    
>>> myGeneratorInstance = myGen()
>>> next(myGeneratorInstance)
Thu, 28 Jun 2001 14:17:15 +0000
>>> next(myGeneratorInstance)
Thu, 28 Jun 2001 14:18:02 +0000

The generator encapsulates an infinite loop, but this isn't a problem because you only get each answer every time you ask for it.

cjrh
+1 - Printing the time illustrates the idea perfectly!
ooboo
+7  A: 

First of all the term generator originally was somewhat ill-defined in Python, leading to lots of confusion. What you probably mean are iterators and iterables (see here). Then in Python there are also generator functions (which return a generator object), generator objects (which are iterators) and generator expressions (which are evaluated to a generator object).

According to http://docs.python.org/glossary.html#term-generator it seems that the official terminology is now that generator is short for "generator function". In the past the documentation defined the terms inconsistently, but fortunately this has been fixed.

It might still be a good idea to be precise and avoid the term "generator" without further specification.

nikow
Hmm I think you're right, at least according to a test of a few lines in Python 2.6. A generator expression returns an iterator (aka 'generator object'), not a generator.
Craig McQueen
A: 

I believe the first appearance of iterators and generators were in the Icon programming language, about 20 years ago.

You may enjoy the Icon overview, which lets you wrap your head around them without concentrating on the syntax (since Icon is a language you probably don't know, and Griswold was explaining the benefits of his language to people coming from other languages).

After reading just a few paragraphs there, the utility of generators and iterators might become more apparent.

Nosredna
+3  A: 

The only thing I can add to Stephan202's answer is a recommendation that you take a look at David Beazley's PyCon '08 presentation "Generator Tricks for Systems Programmers," which is the best single explanation of the how and why of generators that I've seen anywhere. This is the thing that took me from "Python looks kind of fun" to "This is what I've been looking for." It's at http://www.dabeaz.com/generators/.

Robert Rossney
A: 

I found this reference helpful, on Python iterators, generators, and coroutines:

"Tutorial on Python Iterators and Generators" by Norman Matloff, University of California, Davis, 11 May 2009

Craig McQueen
+1  A: 

This post will use Fibonacci numbers as a tool to build up to explaining the usefulness of Python generators.

This post will feature both C++ and Python code.

Fibonacci numbers are defined as the sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ....

Or in general:

F0 = 0
F1 = 1
Fn = Fn-1 + Fn-2

This can be transferred into a C++ function extremely easily:

size_t Fib(size_t n)
{ 
    //Fib(0) = 0
    if(n == 0)
        return 0;

    //Fib(1) = 1
    if(n == 1)
        return 1;

    //Fib(N) = Fib(N-2) + Fib(N-1)
    return Fib(n-2) + Fib(n-1);
}

But if you want to print the first 6 Fibonacci numbers, you will be recalculating a lot of the values with the above function.

For example: Fib(3) = Fib(2) + Fib(1), but Fib(2) also recalculates Fib(1). The higher the value you want to calculate, the worse off you will be.

So one may be tempted to re-write the above by keeping track of the state in main.

//Not supported for the first 2 elements of Fib
size_t GetNextFib(size_t &pp, size_t &p)
{
    int result = pp + p;
    pp = p;
    p = result;
    return result;
}

int main(int argc, char *argv[])
{
    size_t pp = 0;
    size_t p = 1;
    std::cout << "0 " << "1 ";
    for(size_t i = 0; i <= 4; ++i)
    {
        size_t fibI = GetNextFib(pp, p);      
        std::cout << fibI << " ";
    }   
return 0;
}

But this is very ugly, and it complicates our logic in main, it would be better to not have to worry about state in our main function.

We could return a vector of values and use an iterator to iterate over that set of values, but this requires a lot of memory all at once for a large number of return values.

So back to our old approach, what happens if we wanted to do something else besides print the numbers? We'd have to copy and paste the whole block of code in main and change the output statements to whatever else we wanted to do. And if you copy and paste code, then you should be shot. You don't want to get shot do you?

To solve these problems, and to avoid getting shot, we may re-write this block of code using a callback function. Every time a new Fibonacci number is encountered, we would call the callback function.

void GetFibNumbers(size_t max, void(*FoundNewFibCallback)(size_t))
{    
    if(max-- == 0) return;        
    FoundNewFibCallback(0);        
    if(max-- == 0) return;
    FoundNewFibCallback(1);

    size_t pp = 0;
    size_t p = 1;
    for(;;)
    {
        if(max-- == 0) return;
        int result = pp + p;
        pp = p;
        p = result;
        FoundNewFibCallback(result);
    }
}

void foundNewFib(size_t fibI)
{
    std::cout << fibI << " ";
}

int main(int argc, char *argv[])
{
    GetFibNumbers(6, foundNewFib);  
    return 0;
}

This is clearly an improvement, your logic in main is not as cluttered, and you can do anything you want with the Fibonacci numbers, simply define new callbacks.

But this is still not perfect. What if you wanted to only get the first 2 Fibonacci numbers, and then do something, then get some more, then do something else.

Well we could go on like we have been, and we could start adding state again into main, allowing GetFibNumbers to start from an arbitrary point. But this will further bloat our code, and it already looks too big for a simple task like printing Fibonacci numbers.

We could implement a producer and consumer model via a couple threads. But this complicates the code even more.

Instead let's talk about generators.

Python has a very nice language feature that solves problems like these called generators.

A generator allows you to execute a function, stop at an arbitrary point, and then continue again where you left off. Each time returning a value.

Consider the following code that uses a generator:

def fib():
    pp, p = 0, 1  
    while 1:
        yield pp
        pp, p = p, pp+p

g = fib()
for i in range(6):
    g.next()

Which gives us the results:

0
1
1
2
3
5

The yield statement is used in conjuction with Python generators. It saves the state of the function and returns the yeilded value. The next time you call the next() function on the generator, it will continue where the yield left off.

This is by far more clean than the callback function code. We have cleaner code, smaller code, and not to mention much more functional code (Python allows arbitrarily large integers).

Source

Brian R. Bondy
+1  A: 

I like to describe generators, to those with a decent background in programming languages and computing, in terms of stack frames.

In many languages, there is a stack on top of which is the current stack "frame". The stack frame includes space allocated for variables local to the function including the arguments passed in to that function.

When you call a function, the current point of execution (the "program counter" or equivalent) is pushed onto the stack, and a new stack frame is created. Execution then transfers to the beginning of the function being called.

With regular functions, at some point the function returns a value, and the stack is "popped". The function's stack frame is discarded and execution resumes at the previous location.

When a function is a generator, it can return a value without the stack frame being discarded, using the yield statement. The values of local variables and the program counter within the function are preserved. This allows the generator to be resumed at a later time, with execution continuing from the yield statement, and it can execute more code and return another value.

Before Python 2.5 this was all generators did. Python 2.5 added the ability to pass values back in to the generator as well. In doing so, the passed-in value is available as an expression resulting from the yield statement which had temporarily returned control (and a value) from the generator.

The key advantage to generators is that the "state" of the function is preserved, unlike with regular functions where each time the stack frame is discarded, you lose all that "state". A secondary advantage is that some of the function call overhead (creating and deleting stack frames) is avoided, though this is a usually a minor advantage.

Peter Hansen