DEV Community

Eric Garcia
Eric Garcia

Posted on

Understanding Generators - 'lazy' is sometimes better

Modern programming languages are full of interesting features that adapt to a wide variety of needs when we have to implement some functionality to our code.
From various data types and logic statements to class utility concepts like decorators, there's a lot of tools that can improve your code notably.

Today I will try to explain generators in a simple and beginner-friendly way.


What are they used for?

Essentially, generator objects are useful when we want to iterate through some kind of sequence but we don't want (or can't) load it directly in memory (e.g using a list).

Think of a group of buildings in a big city. We want to find the first building that has an underground parking lot.
One option would be to ask to the mayor of the city for a detailed list of all the buildings of the city and wait until he has reviewed all of them and finished the list.
There's a better and more efficient option, which is to ask for the mayor to give us the information of each building that he reviews while he is making the list and keep us updated of each new element (building).
In this case, we could end the research just as soon as we find the first building with an underground parking lot, and the mayor wouldn't have to research all of the buildings.

The size of the list can be interpreted as the system's memory, and in this way we see that generator objects can help us to reduce memory usage in iterating processes.

Performance improvement

Another thing that can be noted in the previous example is that we can work with the data as it is being generated in real time, which is pretty useful for implementing task processing in parallel.
This concept is called lazy evaluation or rather on-demand evaluation.

Code implementation

For the sake of maintaining coherence with the previous example, let's take a similar approach with the code.

def review_buildings(n_buildings):
    for i in range(n_buildings):
        rand_num = random.random()
        if rand_num < 0.2:
            yield True
        else:
            yield False
Enter fullscreen mode Exit fullscreen mode

This is a generator function that yields True if the building has parking and False if not.
For simplicity, the building having parking or not is based of a random probability.
Also, the real function should yield more info about the building and not only the parking boolean value, but let's cut some slack here.

The yield keyword could be seen as the equivalent to the return statement in iterator objects. It works by 'returning' the generated element and keeping track of the iteration evolution (also known as the state).
Using return in a iterator object would render the object non iterable and most probably will return the first result of the sequence.

The lazy evaluation approach would be like this:

for has_parking in review_buildings(50):
    print(has_parking)
    if has_parking:
        break
Enter fullscreen mode Exit fullscreen mode

Notice how this is a much better approach if we just want to find the first building with an underground parking lot because we would not generate -or ask- for more information, as we have found what we needed.

It is important to mention that the use of the break statement here is not encouraged, but serves as a demonstration of the generator utility.

Other characteristics

As python objects and iterators, generators have a wide range of methods and functions that can be pretty useful in some cases.
An example is the next() function, which can be called for getting the next item of the iterator (pretty self-explanatory huh)
aside from that, all functions designed for iterators like sum(), map() etc are specifically made for this objects.

Iterators can also be converted to lists and vice-versa (assuming the iterator object has a finite and defined size) with the list() and the Γ¬ter()` functions.

Real-case iterator example

In one of my projects I had to integrate the answers generated by an LLM client and the user's prompt in an interactive environment. I saw that a nice way to implement it was to iterate indefinitely through a generator object that yields the llm's response each time the user sends their prompt. Feel free to check it and play with it!

Conclusions

Long story short, generator objects are a nice way to iterate through indefinite or large sequences without taking much memory and with the possibilty of being able to work with one value at a time on each iteration.
Hope you found the explanation useful, if you have some doubts or want to give some feedback, don't hesitate to do it!

πŸ‘‹

Top comments (0)