What are generators in Python, and how to use them?

Generators are an important feature of Python that allows for efficient and memory-friendly iteration over large data sequences. Unlike regular functions that return a single value and terminate, generators can produce a sequence of values over time, pausing and resuming as needed.

Generators are special functions that use the yield keyword instead of return to produce a series of values. They are similar to iterators but with simpler syntax and improved memory efficiency. When a generator function is called, it returns an iteratoran object that can be used to loop through collections object that can be used to iterate over the sequence of values produced by the generator.

The yield keyword is used in generator functions to create a generator object. It is a powerful feature that allows the generator function to produce a sequence of values, one at a time, without the need to store the entire sequence in memory.

Iterators and generators in Python
Iterators and generators in Python

Advantages of using generators

  • Memory efficiency: Generators produce values on-the-fly, which means they don’t require storing the entire sequence in memory. This makes them ideal for working with large or infinite sequences.

Memory saving using generator
Memory saving using generator
  • Lazy evaluation: Generators follow the principle of lazy evaluation, where values are computed only when needed. This enables efficient processing of data, especially when not all values are required.

Traditional Vs. Lazy evaluation
Traditional Vs. Lazy evaluation
  • Simplified syntax: Generators provide a simpler and more readable syntax than implementing custom iterator classes. They eliminate the need for maintaining explicit state variables and managing iteration logic.

Syntax of python generator
Syntax of python generator

Implementation

To create a generator, we define a function using the yield keyword. The yield keyword suspends the execution of the function, saves its internal state, and returns a value. The next time the generator’s iterator is called, the function resumes execution from where it left off, continuing until the next yield keyword is encountered.

Let’s understand the concept with some code examples

Example 1: Generating Fibonacci numbers

def fibonacci_generator():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# Usage
fib_gen = fibonacci_generator()
for i in range(10):
print(next(fib_gen))

In this example, we define a generator function fibonacci_generator() that yields Fibonacci numbers indefinitely. We can then create an instance of the generator and use the next() function to retrieve the next value from the sequence.

Generators are one-time iterators, meaning that once a generator is iterated over, it cannot be restarted or reused. If you need to iterate over the sequence multiple times, you will need to create a new generator object.

Example 2: Filtering even numbers

def even_numbers(start, end):
for num in range(start, end + 1):
if num % 2 == 0:
yield num
# Usage
even_gen = even_numbers(1, 10)
for num in even_gen:
print(num)

Here, the even_numbers() generator function generates even numbers within a given range. The generator filters out odd numbers using a conditional statement, yielding only even numbers. We can then iterate over the generator to print the filtered values.

Example 3: Reading large files

main.py
large_data.txt
def read_large_file(file_path):
with open(file_path, 'r') as file:
for line in file:
yield line.strip()
# Example process_line function
def process_line(line):
print(line)
# Usage
large_file_gen = read_large_file('large_data.txt')
for line in large_file_gen:
process_line(line)
## break;

This example demonstrates the use of generators to read large files. Instead of loading the entire file into memory, the read_large_file() generator function reads and yields one line at a time. This approach is memory-efficient and enables processing large files without overwhelming system resources.

Note: Generators are designed to produce values on-demand and avoid storing the entire sequence in memory. If you explicitly convert a generator to a list using the list() function, it will consume memory as it generates all the values at once. Be mindful of memory usage when working with large or infinite sequences.

Conclusion

Generators provide a powerful and memory-efficient way to iterate over sequences of data in Python. By using the yield keyword, we can create functions that produce values on-the-fly, allowing for lazy evaluation and reduced memory footprint. Generators are particularly useful when working with large or infinite sequences and can greatly enhance the performance of your code.

Free Resources

Copyright ©2025 Educative, Inc. All rights reserved