Lesson 57 of 70 – Python Generators
81%

Python Generators

A generator is a special type of iterator that allows Python to produce values one at a time instead of storing all the values in memory at once.

Generators are commonly created using a function with the yield keyword.

Note: A generator function uses yield instead of return when it needs to produce a sequence of values one at a time.
What is a Generator?

A generator is an object that generates values one by one when they are requested.

Unlike a normal function, a generator function does not return all results at once. It pauses its execution at each yield statement and continues from that point when the next value is requested.

def numbers():

    yield 1
    yield 2
    yield 3


for number in numbers():
    print(number)
Output:
1
2
3
What is yield?

The yield keyword is used to produce a value from a generator.

When Python reaches yield, the generator pauses its execution and remembers its current state.

def demo():

    yield 10
    yield 20
    yield 30

Each time the generator requests another value, execution continues from where it previously paused.

Simple Generator Example
def count():

    yield 1
    yield 2
    yield 3
    yield 4
    yield 5


for value in count():
    print(value)
Output:
1
2
3
4
5
Generator and next()

A generator is an iterator, so we can use the next() function with it.

def numbers():

    yield 10
    yield 20
    yield 30


generator = numbers()

print(next(generator))
print(next(generator))
print(next(generator))
Output:
10
20
30
Generator Pauses Execution

A generator pauses when it reaches a yield statement.

def demo():

    print("Start")

    yield 10

    print("Middle")

    yield 20

    print("End")


generator = demo()

print(next(generator))
print(next(generator))
Output:
Start
10
Middle
20

The generator continues from the point where it was previously paused.

Generator vs Normal Function
Normal Function Generator Function
Usually uses return. Uses yield.
Returns a result and ends. Pauses and can continue later.
Can return a complete collection. Produces values one at a time.
May require more memory for large collections. Can be memory-efficient for large sequences.
Generator with a Loop
def even_numbers():

    for number in range(2, 11, 2):
        yield number


for number in even_numbers():
    print(number)
Output:
2
4
6
8
10
Generator for Squares
def squares(limit):

    for number in range(1, limit + 1):
        yield number * number


for value in squares(5):
    print(value)
Output:
1
4
9
16
25
Generator with Parameters

A generator function can accept parameters just like a normal function.

def numbers(start, end):

    for number in range(start, end + 1):
        yield number


for value in numbers(5, 9):
    print(value)
Output:
5
6
7
8
9
Generator with a Condition
def even_numbers(start, end):

    for number in range(start, end + 1):

        if number % 2 == 0:
            yield number


for value in even_numbers(1, 10):
    print(value)
Output:
2
4
6
8
10
Generator Expression

Python also provides generator expressions. Their syntax is similar to list comprehensions, but they use parentheses instead of square brackets.

numbers = (x * x for x in range(1, 6))

for number in numbers:
    print(number)
Output:
1
4
9
16
25
List Comprehension vs Generator Expression
numbers_list = [x * x for x in range(1, 6)]

numbers_generator = (x * x for x in range(1, 6))
List Comprehension Generator Expression
Uses []. Uses ().
Creates the list immediately. Produces values when requested.
Stores all generated values. Can avoid storing all values at once.
Generator and StopIteration

When a generator has no more values to produce, iteration ends. Calling next() after the generator is exhausted raises StopIteration.

def numbers():

    yield 1
    yield 2


generator = numbers()

print(next(generator))
print(next(generator))
print(next(generator))
Result:
1
2
StopIteration
Generator for Large Data

Generators are especially useful when working with large amounts of data.

def numbers(limit):

    for number in range(1, limit + 1):
        yield number


for number in numbers(1000000):

    if number > 5:
        break

    print(number)
Output:
1
2
3
4
5

The generator produces values as needed instead of creating a million-value list first.

Memory Efficiency

A generator can be memory-efficient because it produces one value at a time.

def numbers():

    for number in range(1, 6):
        yield number


generator = numbers()

for number in generator:
    print(number)

Only the value currently requested needs to be produced.

Important: Generators are useful when the complete collection does not need to exist in memory at the same time.
Generator for Reading Files

Generators can be useful for processing files line by line.

def read_lines(filename):

    with open(filename, "r") as file:

        for line in file:
            yield line.strip()

The function produces one line at a time rather than creating a separate list containing every line.

Generator Pipeline

Generators can be combined so that the output of one stage becomes the input of another stage.

def numbers():

    for number in range(1, 11):
        yield number


def squares(values):

    for value in values:
        yield value * value


result = squares(numbers())

for value in result:
    print(value)
Output:
1
4
9
16
25
36
49
64
81
100
Generator with Multiple yield Statements

A generator function can contain multiple yield statements.

def colors():

    yield "Red"
    yield "Green"
    yield "Blue"


for color in colors():
    print(color)
Output:
Red
Green
Blue
Generator with send()

Generators also support the send() method, which can send a value into a paused generator.

def calculator():

    total = 0

    while True:

        value = yield total

        total += value


generator = calculator()

print(next(generator))

print(generator.send(10))

print(generator.send(20))
Output:
0
10
30

The send() method resumes the generator and provides a value to the yield expression.

Generator with return

A generator can also use return to finish its execution.

def numbers():

    yield 1
    yield 2
    return


for number in numbers():
    print(number)
Output:
1
2

The return statement ends the generator. A return value can also be carried by the resulting StopIteration exception.

Generator vs Iterator
Generator Custom Iterator
Usually created using yield. Usually created by implementing __iter__() and __next__().
Python manages much of the iterator state automatically. The programmer manages the iterator state.
Usually requires less code. Can require more code.
Is itself an iterator. Implements the iterator protocol.
Common Uses of Generators
  • Processing large files.
  • Generating large sequences.
  • Processing data streams.
  • Reading data line by line.
  • Creating data pipelines.
  • Working with large datasets.
  • Producing values on demand.
Complete Generator Example
def multiplication_table(number, limit):

    for i in range(1, limit + 1):

        yield number * i


table = multiplication_table(5, 10)

for value in table:
    print(value)
Output:
5
10
15
20
25
30
35
40
45
50
Advantages of Generators
  • Produce values one at a time.
  • Can reduce memory usage for large sequences.
  • Easy to create using yield.
  • Automatically support the iterator protocol.
  • Useful for large or continuous data processing.
  • Can be combined to create efficient data pipelines.
Key Points
  • A generator is a special type of iterator.
  • Generator functions use the yield keyword.
  • yield pauses execution and preserves the generator's state.
  • next() requests the next generated value.
  • Generators produce values on demand.
  • Generators can be more memory-efficient than building large lists.
  • Generator expressions use parentheses.
  • Generators are useful for files, streams and large datasets.
  • A generator ends when it has no more values to produce.
  • StopIteration indicates that iteration is complete.

🧠 Quick Quiz

Question: Which keyword is used to create a generator function in Python?