Generators in Python

Generators in Python

In Python, a generator is a special type of iterable, similar to a list or a tuple, but with a few key differences. Unlike lists or tuples, generators do not store their contents in memory. Instead, they generate values on the fly as they are requested.

The generator is a type of iterator that generates a sequence of values on the fly, rather than storing all of the values in memory at once. This can be useful for working with large or infinite sequences of data, as it allows you to work with one value at a time rather than trying to load everything into memory at once.

Generators are useful when dealing with large datasets or infinite sequences, where it would be impractical or impossible to generate all the values at once and store them in memory. By generating values on the fly as they are needed, generators allow us to work with these datasets efficiently and without running out of memory.

There are several reasons why you might want to use a generator in Python:

  • Memory efficiency: Generators allow you to generate and process large or infinite sequences of data without having to load everything into memory at once. This can be useful when dealing with very large datasets or when working with streams of data that are too large to fit in memory.

  • Lazy evaluation: Generators evaluate data on-the-fly, as it is requested, rather than generating all of the data at once. This can be useful when you only need to access a portion of a larger dataset, or when you don't want to spend time generating data that you may not end up using.

  • Simplify code: Generators can simplify code by allowing you to express complex operations as a sequence of simple generator functions, rather than having to create and manage more complex data structures.

  • Improved performance: Because generators allow you to perform operations on data as it is generated, they can often result in improved performance and reduced memory usage compared to more traditional programming techniques.

  • Interoperability: Generators are iterable, which makes them easy to work within a variety of different contexts, including loops, list comprehensions, and other iterable-based operations.

Overall, generators can be a powerful tool for working with large, complex, or infinite sequences of data in a flexible, efficient, and memory-friendly way.


example:

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

gen = fibonacci()

print(gen)

In this code, a generator function called fibonacci() is defined which generates the Fibonacci sequence indefinitely. The yield keyword is used to yield each value in the sequence as it is generated.

Then, the generator function is called and assigned to the variable gen. This creates a generator object that can be used to generate the Fibonacci sequence.

Finally, the print() function is called with the gen object as its argument. However, this will not actually print the Fibonacci sequence. Instead, it will print a string representation of the generator object, which will look something like <generator object fibonacci at 0x7fc60cc425e0>.

To actually generate the Fibonacci sequence, you can use the generator object to iterate over the sequence and generate each value on-the-fly, like so:

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


gen = fibonacci()

counter = 0
while counter < 10:
    print(next(gen))
    counter += 1

This code will generate the first 10 numbers in the Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34. Note that the next() function is used to retrieve the next value in the sequence each time through the loop.

another way:

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

gen = fibonacci()

print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))

There are six next() calls in this code, so the first six numbers in the Fibonacci sequence will be generated and printed to the console.

The generator object gen keeps track of the current state of the fibonacci() function, so each next() call will pick up where the previous call left off.

Alternatively, you can use the itertools.islice() function to get a slice of the sequence:

import itertools

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

gen = fibonacci()

slice = itertools.islice(gen, 10)
print(list(slice))

  • The islice() function takes two arguments: the generator object to slice, and the number of items to include in the slice. In this case, the slice includes the first 10 items in the sequence.

  • The list() function is used to convert the slice object to a list, so that it can be printed.

When this code is run, the output will be a list of the first 10 numbers in the Fibonacci sequence: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34].

Using itertools.islice() is a convenient way to get a slice of an infinite generator object without having to generate the entire sequence. By only generating the items you need, you can save memory and processing time, especially when working with large or infinite sequences.


This generator function generates the Fibonacci sequence indefinitely. Each time the generator is called, it yields the next value in the sequence.

Note that because the fibonacci() function is a generator, it doesn't actually generate the entire sequence of Fibonacci numbers all at once. Instead, it generates each number on-the-fly as it is requested, which makes it much more memory-efficient and flexible than other techniques for generating large sequences of numbers.

Gif description

thank you.