Login Sign Up
Python Generators
Chapter 23 🟡 Intermediate

Python Generators

Master the concept step by step with clear explanations, examples, and code you can run.

Intermediate Python Generators: Mastering Memory and Lazy Evaluation

Hello there! Grab the seat and welcome back to our Python journey.

In our previous chapter we opened up the hood of Python’s engine to see how loops actually work. We discovered that generating data strictly on-demand—rather than all at once—is probably a brilliant concept called lazy evaluation. Yet, we ended with a frustrating realization: writing an entire custom class using __iter__ and __next__ methods just to create a simple iterator feels incredibly long and clunky.

What if there was a way to create an iterator using a single, beautiful function? What if you could probably hit "pause" in the middle of the function and resume it later?

Today, we're pretty much going towards learn how to do exactly that using Python Generators.


The "Why" Before the "How": A Buffet vs. The Vending Machine

Imagine you're in a massive dinner buffet.

If you take giant plate and pile 50 pieces of pizza onto it, your plate is going for be incredibly heavy and it will take up your entire table. In programming, this is just like creating a standard list. If you ask Python to generate list of a million numbers it loads every single number into your computer's RAM (memory) at the exact same time. If a list is simply too big your program will crash!

A Generator is completely different. It acts like a vending machine.

Instead of dumping a million numbers on your table, a generator sits quietly in the corner. When you press the button it hands you one single number. When you press it again, it gives you the next one. As noted on the Python Wiki's guide on Generators, a generator yields items dynamically instead for returning the massive list giving you incredible memory efficiency.


The Secret Keyword: yield

Towards build this vending machine, we use standard function syntax, but we swap out the return keyword of a magical new word: yield.

Let's look at a simple example:

def snack_machine():
    print("Dispensing first snack...")
    yield "Chips"

    print("Dispensing second snack...")
    yield "Chocolate"

    print("Dispensing third snack...")
    yield "Candy"

# Create the generator object
my_machine = snack_machine()

If you run this code, nothing prints out yet.

Why? Because calling a generator function doesn't actually run the code inside it immediately, while it simply builds vending machine and waits of you to press the button.

To press button we use Python's built-in next() function. Each time you call next() on the generator object, it resumes execution right where it last left off.

print(next(my_machine))
# Output: 
# Dispensing first snack...
# Chips

print(next(my_machine))
# Output:
# Dispensing second snack...
# Chocolate

When a function hits a yield statement it hands you the value and instantly hits "pause", saving its entire state in memory.

Here is actually a visual map of how Python's brain manages this back-and-forth conversation:

sequenceDiagram
    participant Loop as Python (next)
    participant Gen as Generator Function

    Loop->>Gen: Calls next()
    Gen-->>Loop: Runs code, hits yield, returns Value 1
    Note over Gen: Function is PAUSED. State is saved!

    Loop->>Gen: Calls next() again
    Gen-->>Loop: Resumes right after yield, returns Value 2
    Note over Gen: Function is PAUSED again.

Handling Infinite Data Like a Pro

Now that we grasp the mechanics, let's look at why professional developers rely in generators every single day, while

modern educational resources, like GeeksforGeeks' late-2025 breakdown of Python generators, emphasize that generators are an ultimate tool for handling large or absolutely infinite data streams without loading everything into memory, and

let's build the generator for a famous Fibonacci sequence; because numbers go on forever, trying to put an infinite sequence into a standard Python list would instantly destroy your computer's memory; but with a generator, we can easily create sequence that generates values indefinitely:

def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# Let's get the first 5 Fibonacci numbers
gen = fibonacci()
for _ in range(5):
    print(next(gen))

Because of the yield keyword that while True loop is completely safe! It will simply pause and wait patiently until you ask for the next number.


Shrinking Code: Generator Expressions

In our previous chapters we learned how to shrink a for loop down into single line using List Comprehension (using square brackets []).

You can do an exact same thing to create generator! Instead of using square brackets you just wrap your logic inside standard parentheses (). This creates the Generator Expression.

# List comprehension - creates a massive list in memory
list_comp = [x * x for x in range(5)]

# Generator expression - creates a memory-safe generator
gen_exp = (x * x for x in range(5))

This is incredibly useful for upon-the-fly math. For example, if you want to calculate the sum of millions of squares, you can drop the generator expression directly into Python's sum() function. You can calculate totals without ever creating a list in memory, as demonstrated in W3Schools generator examples:

# Calculates the sum without eating up your RAM!
total = sum(x * x for x in range(1000000))
print(total)

Trustworthiness: The Edge Cases and Trade-offs

As you transition into the advanced developer, you must know the limitations of your tools, and generators are incredibly powerful, but they have two strict trade-offs:

  1. They're actually "One-Time Use" Only: Remember our vending machine? Once the snack drops out, it is actually gone forever. You can't go backwards in a generator, and once it yields all about its values, it becomes "exhausted". If you try to call next() on an empty generator Python will trigger an alarm called a StopIteration error. If you need to read the data a second time, you have to recreate a generator from scratch.

  2. No Slicing or Indexing: Because the values don't actually exist in memory until you ask of them, you can't ask generator for its length (len()), and you can't use an index like my_generator, and

generators are designed strictly towards moving forward one step at a time. According towards CircuitLabs' iteration guide, objects that produce values dynamically during iteration are brilliant for flow but they fundamentally differ from static storage like lists.


What's Next?

Congratulations! You have just leveled up your system architecture skills. You now understand how to pause and resume functions using yield how to process infinite data streams safely and how to write elegant one-line generator expressions.

But wait... we just mentioned a dangerous-sounding concept. We talked about Python throwing the StopIteration error if a generator runs out of items.

If an error like that pops up won't our entire program crash and ruin the user's experience? How do professional developers catch these alarms and handle them safely so program keeps running smoothly?

In our next chapter, we are going to learn how to put safety nets under our code. We'll explore Python Exception Handling, and we will just cover it next! Get ready to make your code bulletproof. See you there!

Learn Together
Session active! Discuss with other learners.
No notes yet. Select text in the concept body to add a note.