yield#

The yield is a keyword in python that is used to return from a function while preserving the state of its local variables, and when such a function is called again, execution continues from the yield statement where it was interrupted. Any function containing the yield keyword is called a generator. We can say that yield is what makes it a generator.


The followoing cell defines generator that iterates over list but returns only those values that are even. It also prints the corresponding message before returning.

def even_generator(list_of_nums: list[int]):
    for i in list_of_nums:
        if i % 2 == 0:
            print(f"Returning {i}")
            yield i

Next code demonstrates the direct result of the generator call.

generator_object = even_generator([1, 2, 4, 7, 10])
generator_object
<generator object even_generator at 0x7e93fc460900>

It’s an object that will produce a result when the function next is applied to it.

print(next(generator_object), next(generator_object))
Returning 2
Returning 4
2 4

Applying the list function to it simply collects all the outputs into one list object.

list(even_generator([1, 2, 4, 7, 10]))
Returning 2
Returning 4
Returning 10
[2, 4, 10]

Standard output messages indicate that the next element is computed only when the next call is applied to the generator. This is actually a really important feature - you can operate on arrays just as you would with regular iterable objects, but they are not necessary in memory, which is imporant when you need to save memory.

Generator object#

Functions that exit using yield return a generator. A generator is a special kind of iterator that represents a “frozen” procedure, which resumes execution when the program requests the next element.


The following example simply iterates over a list of numbers using the yield operator and prints each element.

def experiment_fun(list_of_nums):
    for v in list_of_nums:
        print(f"processing object: {v}")
        yield v

list_of_nums = [10,20,30,40,50]

print("Function execution:")
gen = experiment_fun(list_of_nums)
print("Result:", gen, end = "\n\n")

print("Generator unpacking:")
print("Result:", list(gen))
Function execution:
Result: <generator object experiment_fun at 0x7fada84d4b30>

Generator unpacking:
processing object: 10
processing object: 20
processing object: 30
processing object: 40
processing object: 50
Result: [10, 20, 30, 40, 50]

The key idea here is that

  • During the function call, the function wasn’t actually executed - we don’t have any output from print, and we got the generator object as a result;

  • But when we unpacked the generator into a list, we got messages and a list as a result.

yield and return#

But what happens if you use both yield and return in the same function? Nothing special - the function will create generator, but if it has return in it - it will just stop iterating over generator.


The following cell just shows how it might look - it iterates over the passed array while for the first three elements, but then return is executed so that elements after the third have not been processed.

def experiment_fun(list_of_nums):
    for i, v in enumerate(list_of_nums):
        print(f"processing object: {v, i}")
        
        yield v
        
        if i > 1:
            return "test"

list_of_nums = [10,20,30,40,50]
list(experiment_fun(list_of_nums))
processing object: (10, 0)
processing object: (20, 1)
processing object: (30, 2)
[10, 20, 30]