Back to Python
2026-03-135 min read

Python Generators

Learn Python Generators step by step with clear examples and exercises.

Why This Matters

Python generators are an essential tool for managing large amounts of data efficiently, without consuming excessive memory or processing power. They allow you to process data on-the-fly, reducing the need for storing all data in memory at once. This is particularly useful when dealing with streaming data from APIs or databases, as it enables real-time processing and analysis.

Moreover, Python generators can improve your code's performance by minimizing the number of objects created during execution. By producing values one at a time instead of generating an entire list at once, you can save both memory and CPU resources.

Advantages of Using Generators

  • Improved memory usage: Generators produce values on demand, reducing the need to store all data in memory simultaneously.
  • Reduced processing time: By producing values one at a time, generators allow for faster execution times compared to generating entire lists or arrays.
  • Efficient resource utilization: Generators consume fewer resources, making them ideal for handling large datasets and complex computations.

Prerequisites

To understand Python generators, you should have a solid grasp of the following concepts:

  • Basic Python syntax and data structures (variables, lists, tuples, dictionaries)
  • Functions and function definitions
  • Loops (for and while loops)
  • List comprehensions
  • Exception handling

Core Concept

A generator is a special type of function in Python that returns an iterator object. Instead of returning a single value or multiple values as a list, a generator produces values one at a time when iterated upon. This on-demand approach allows for more efficient memory usage and faster processing times.

Defining Generators

To create a generator function, you use the yield keyword instead of the return keyword. Here's an example:

def fibonacci(n):
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b

In this example, the fibonacci function generates the Fibonacci sequence up to the specified number (n). The yield keyword is used to produce each Fibonacci number as an output. When you iterate over the generator object, it will return the next Fibonacci number until reaching the end of the sequence or encountering a break statement.

Iterating Over Generators

To use a generator, you can call it like any other function and assign its result to an iterator variable. You can then iterate over this iterator using a for loop:

fib = fibonacci(10) # Create the generator object
for number in fib: # Iterate over the generator object
print(number) # Print each Fibonacci number

Generator Expressions

In addition to defining generators using functions, you can also create them using generator expressions. These are similar to list comprehensions but use parentheses instead of square brackets:

fib_gen = (a + b for a, b in zip([0, 1], [1, *itertools.islice(fibonacci(10), 1, None)]) if a is not None)

In this example, we create a generator expression that generates the Fibonacci sequence from the fibonacci function and consumes only one value (the first Fibonacci number). The rest of the sequence is generated on demand as you iterate over the resulting generator.

Worked Example

Let's create a generator that generates prime numbers up to a specified limit:

def primes(limit):
for num in range(2, limit + 1):
if all([num % i != 0 for i in range(2, int(num ** 0.5) + 1)]):
yield num

Generate prime numbers up to 100

primes_under_100 = list(primes(100))

print(primes_under_100)


In this example, the `primes` generator checks whether a number is prime by verifying that it has no divisors below its square root. The generated prime numbers are then stored in a list for easy access and printing.

Common Mistakes

  1. Forgetting to use yield: To create a generator, you must replace the return keyword with yield.
  2. Iterating over generators multiple times: Generators are stateful and maintain their internal state between iterations. Reiterating over the same generator will produce incorrect results.
  3. Using generators inappropriately: Generators can be used to optimize memory usage and improve performance, but they should not be used indiscriminately. Carefully consider whether a list or a generator is more appropriate for your use case.
  4. Not handling exceptions correctly: If an error occurs within the generator function, it may cause unexpected behavior when iterating over the generator. Ensure that your generator handles potential errors gracefully.
  5. Confusing generators with coroutines: While both are used to manage resources efficiently, they serve different purposes and have distinct syntaxes. Coroutines use the yield from statement instead of the yield keyword.
  6. Not understanding generator exhaustion: Generators can be exhausted when all values have been produced or an error occurs within the generator function. To check if a generator is exhausted, you can use the generator.__iter__().__next__() method to raise a StopIteration exception.

Practice Questions

  1. Write a generator function that generates all even numbers up to a specified limit.
  2. Modify the Fibonacci generator example to generate the first n Fibonacci numbers instead of generating the sequence up to a specific number.
  3. Create a generator that generates all palindromic numbers between 1 and 100.
  4. Write a generator that generates all permutations of a given string.
  5. Implement a generator that produces all combinations of a given list, where order does not matter (i.e., similar to itertools.combinations but without preserving the original order).
  6. Create a generator that yields the first n prime numbers.
  7. Write a generator that generates Fibonacci numbers up to a specified limit, but only when the number is odd.
  8. Implement a generator that generates all subsets of a given set (including the empty subset and the original set).
  9. Create a generator that yields the first n prime numbers greater than a specified value.
  10. Write a generator that generates all permutations of a list, preserving the original order.

FAQ

How do I check if an object is a generator?

You can use the isinstance() function to check if an object is a generator:

if isinstance(my_object, type(iter(range(10)))):
print("This is a generator.")
else:
print("This is not a generator.")

Can I convert a list to a generator?

Yes, you can create a generator from an existing list using the built-in enumerate() function in conjunction with the __next__() method:

my_list = [1, 2, 3, 4, 5]
my_generator = (x for x in my_list)
next(my_generator) # Get the first element
next(my_generator) # Get the second element

And so on...


### How do I combine multiple generators?

You can use list comprehensions or the `itertools.chain()` function to combine multiple generators into a single generator:

def fibonacci(n):

a, b = 0, 1

for _ in range(n):

yield a

a, b = b, a + b

def primes(limit):

Generator function to generate prime numbers up to limit

Combine the two generators using list comprehension

combined_gen = (x for x in fibonacci(10) if x in primes(100))

print(list(combined_gen)) # Print combined generator's output as a list

Python Generators | Python | XQA Learn