Why do we write generator functions in Python?

Published on Aug. 22, 2023, 12:20 p.m.

We write generator functions in Python because they allow us to create iterators in a simpler and more memory-efficient way than traditional iterators.

A generator function is a special type of function that uses the “yield” keyword to temporarily suspend its execution and produce a value every time it is called. This allows us to create a sequence of values “on-the-fly” rather than generating all values at once and storing them in memory.

Generator functions can be very useful for handling large datasets or for creating iterable objects that are too large to fit into memory. By using generator functions, we can create an iterator that produces the sequence of values we need one at a time, as we need them.

In addition, generator functions can make our code simpler and more readable, since we don’t need to write complex code to handle iterators manually.

example of the difference between using a generator function and not using one in Python

Sure, here is an example of the difference between using a generator function and not using one in Python.

Without generator function:

def square_numbers(nums):
    result = []
    for n in nums:
        result.append(n * n)
    return result

my_nums = [1, 2, 3, 4, 5]
print(square_numbers(my_nums))

With generator function:

def square_numbers(nums):
    for n in nums:
        yield n * n

my_nums = [1, 2, 3, 4, 5]
print(list(square_numbers(my_nums)))

In the first example, we define a function that takes a list of numbers, computes the square of each number, and returns a list of squared numbers. However, this approach is not memory-efficient if we have a large input list, since we have to store the entire list of squared numbers in memory before returning it.

In the second example, we define a generator function that does the same thing but uses the “yield” keyword to temporarily suspend the function’s execution and produce the squared value on demand. This approach is more memory-efficient since it produces the values one at a time and only keeps one value in memory at a time.

By using a generator function, we can write more efficient and elegant code that can handle large or infinite sequences of values.

how the memory usage differs between a generator function and a non-generator function in Python

Sure, here’s an example of how the memory usage differs between a generator function and a non-generator function in Python:

Without generator function:

import sys

def square_numbers(nums):
    result = []
    for n in nums:
        result.append(n * n)
    return result

my_nums = [1, 2, 3, 4, 5]
my_squares = square_numbers(my_nums)
print(sys.getsizeof(my_squares))

With generator function:

import sys

def square_numbers(nums):
    for n in nums:
        yield n * n

my_nums = [1, 2, 3, 4, 5]
my_squares = square_numbers(my_nums)
print(sys.getsizeof(my_squares))

In the first example, we compute the square of each number in the input list and return a new list of squared numbers. We then store the resulting list in the variable “my_squares” and use the sys.getsizeof() function to check its memory usage. This approach results in a relatively large memory usage, since we are storing the entire list of squared numbers in memory at once.

In the second example, we define a generator function that does the same thing but produces the squared values one at a time using the “yield” keyword. We then store the generator object in the variable “my_squares” and use the sys.getsizeof() function to check its memory usage. This approach results in a relatively small memory usage, since we only keep one squared value in memory at a time.

By using a generator function, we can write more memory-efficient code that can handle large or infinite sequences of values.