Python Language


Generators All Versions

Python 2.x

2.0
2.1
2.2
2.3
2.4
2.5
2.6
2.7

Python 3.x

3.0
3.1
3.2
3.3
3.4
3.5
3.6

This draft deletes the entire topic.

Introduction

Introduction

expand all collapse all

Examples

  • 57

    Generator functions are similar to regular functions, except that they have one or more yield statements in their body. Such functions cannot return any values (however empty returns are allowed if you want to stop the generator early). Generator expressions are similar to list, dictionary and set comprehensions, but are enclosed with parentheses. Those do not have to be present when they are used as the sole argument for a function call.

    def nums():  # Generator function
        print("Yielding 1")
        yield 1
        print("Yielding 2")
        yield 2
        print("Finishing")
    
    even_nums = (i for i in range(10) if i % 2 == 0)  # Generator expression
    print(list(even_nums))
    
    print(sum(i for i in range(10) if i % 2 == 0))
    # Generator expression without parentheses
    

    Calling a generator function produces a generator object, which can later be iterated over. Unlike other types of iterators, generator objects may only be traversed once.

    g1 = nums()
    print(g1)  # Out: <generator object nums at 0x1012e1888>
    

    Notice that a generator's body is not immediately executed: when you call nums() in the example above, it immediately returns a generator object, without executing even the first print statement. This allows generators to consume less memory than functions that return a list, and it allows creating generators that produce infinitly long sequences.

    For this reason, generators are often used in data science, and other contexts involving large amounts of data. Another advantage is that other code can immediately use the values yielded by a generator, without waiting for the complete sequence to be produced.

    However if you need to use the values produced by a generator more than once, and if generating them costs more than storing, it may be better to store the yielded values as a list than to re-generate the sequence.

    Typically a generator object is used in a loop, or in any function that requires an iterable:

    for num in g1:
        print("Received %r" % num)
    
    # Output:
    # Yielding 1
    # Received 1
    # Yielding 2
    # Received 2
    # Finishing
    
    arr1 = list(g1)
    # arr1 = [], because the loop above already consumed all the values.
    g2 = nums()
    arr2 = list(g2)  # arr2 = [1, 2]
    

    Since generator objects are iterators, one can iterate over them manually using the next() function. It will return the yielded values one by one on each subsequent invocation.

    Under the hood, each time you call next() on a generator, Python executes statements in the body of the generator function until it hits the next yield statement. At this point it returns the argument of the yield command, and remembers the point where that happened. Calling next() once again will resume execution from that point and continue until the next yield statement.

    If Python reaches the end of the generator function without encountering any more yields, a StopIteration exception is raised (this is normal, all iterators behave in the same way).

    g3 = nums()
    x = next(g3)  # prints "Yielding 1", x becomes 1
    y = next(g3)  # prints "Yielding 2", y becomes 2
    z = next(g3)  # prints "Finishing", then raises StopIteration; z remains undefined
    

    Note that in Python 2 generator objects had .next() method that could be used to iterate through the yielded values manually. In Python 3 this method was replaced with .__next__() standard for all iterators.

  • 32

    Generators can be used to represent infinite sequences:

    def integers_starting_from(n):
        while True:
            yield n
            n += 1
    
    natural_numbers = integers_starting_from(1)
    

    Infinite sequence of numbers as above can also be generated with the help of itertools.count. The above code could be written as below

    natural_numbers = itertools.count(1)
    

    You can use generator comprehensions on infinite generators to produce new generators:

    multiples_of_two = (x * 2 for x in natural_numbers)
    multiples_of_three = (x for x in natural_numbers if x % 3 == 0)
    

    Be aware that an infinite generator does not have an end, so passing it to any function that will attempt to consume the generator entirely will have dire consequences:

    list(multiples_of_two)  # will never terminate, or raise an OS-specific error
    

    Instead, use list/set comprehensions with range (or xrange for python < 3.0):

    first_five_multiples_of_three = [next(multiples_of_three) for _ in range(5)] 
    # [3, 6, 9, 12, 15]
    

    or use itertools.islice() to slice the iterator to a subset:

    from itertools import islice
    multiples_of_four = (x * 4 for x in integers_starting_from(1))
    first_five_multiples_of_four = list(islice(multiples_of_four, 5))
    # [4, 8, 12, 16, 20]
    

    Note that the original generator is updated too, just like all other generators coming from the same "root":

    next(natural_numbers)    # yields 16
    next(multiples_of_two)   # yields 34
    next(multiples_of_four)  # yields 24
    

    An infinite sequence can also be iterated with a for-loop. Make sure to include a conditional break statement so that the loop would terminate eventually:

    for idx, number in enumerate(multiplies_of_two):
        print(number)
        if idx == 9:
            break  # stop after taking the first 10 multiplies of two
    

    Classic example - Fibonacci numbers

    import itertools
    
    def fibonacci():
        a, b = 1, 1
        while True:
            yield a
            a, b = b, a + b
    
    first_ten_fibs = list(itertools.islice(fibonacci(), 10))
    # [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
    
    def nth_fib(n):
        return next(itertools.islice(fibonacci(), n - 1, n))
    
    ninety_nineth_fib = nth_fib(99)  # 354224848179261915075
    
  • 26

    In addition to receiving values from a generator, it is possible to send an object to a generator using the send() method.

    def accumulator():
        total = 0
        value = None
        while True:
            # receive sent value
            value = yield total
            if value is None: break
            # aggregate values
            total += value
    
    generator = accumulator()
    
    # advance until the first "yield"
    next(generator)      # 0
    
    # from this point on, the generator aggregates values
    generator.send(1)    # 1
    generator.send(10)   # 11
    generator.send(100)  # 111
    # ...
    
    # Calling next(generator) is equivalent to calling generator.send(None)
    next(generator)      # StopIteration
    

    What happens here is the following:

    • When you first call next(generator), the program advances to the first yield statement, and returns the value of total at that point, which is 0. The execution of the generator suspends at this point.
    • When you then call generator.send(x), the interpreter takes the argument x and makes it the return value of the last yield statement, which gets assigned to value. The generator then proceeds as usual, until it yields the next value.
    • When you finally call next(generator), the program treats this as if you're sending None to the generator. There is nothing special about None, however, this example uses None as a special value to ask the generator to stop.
Please consider making a request to improve this example.

Syntax

  • yield <expr>
  • yield from <expr>
  • <var> = yield <expr>
  • next(<iter>)

Parameters

Parameters

Remarks

Remarks

Still have a question about Generators? Ask Question

Topic Outline