A generator is a function that when called, runs from its last position to the next yield x, outputs x, and then stop until it is called again.
A generator can have a finite number of execution, for instance when using a for loop or when reading a file, or an infinite number when using a while loop.
Generators are convenient as data on which they iterate do not need to be stored. For instance:
for i in [1, 2, 3, 4, 5, 6]for i in range(1, 7)In the first case, the elements from 1 to 6 are stored in a list, so 6 elements are stored in the memory.
In the second case, the elements are not stored. We call the range generator, that outputs at each round the next value until reaching 6
The syntax for quick and easy generator is very similar to list generation:
[i for i in range(4, 20)] generate a list(i for i in range(4, 20)) generate a generator.Compare:
dic = {"a": 32, "b": 34, "c": 50}
for c in list(dic):
    if c == "b":
        del(dic[c])
And
dic = {"a": 32, "b": 34, "c": 50}
for c in dic.keys(): # equivalent to "for c in dic:"
    if c == "b":
        del(dic[c])
The first example runs correctly, and you end up with {"a": 32, "c": 50}.
The second throws an error.
The first example works because you have dic and you ask first to evaluate your generator using list(dic) (which is equivalent to [k for k in dic]).
Because when the for loop runs, the dic keys are already been extracted, there is no problem when discarding one element of the dic.
In the second case, this is a generator. A generator may or may not accept to handle modification during its execution. Here, it does not.
def my_generator(x):
    print("This is called only once")
    for i in range(x, 12):
        yield i
gen = my_generator(3)
print(next(gen))
>>> This is called only once
>>> 3
next(gen)
>>> 4
for i in gen:
    print(i)
>>> 5
>>> 6
>>> 7
>>> 8
>>> 9
>>> 10
>>> 11
When the generator is out of elements:
next(gen)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
Cell In[6], line 1
----> 1 next(gen)
StopIteration:
Works as finite generators. The only difference is that you never reach the end.
def my_generator_infinite(x):
    while True:
        x += 1
        yield x
gen = my_generator_infinite(2)
for i in gen:
    print(i)
>>> 3 # +1 applied BEFORE the `yield x`
>>> 4
>>> 5
...
There are three operations that can be done on the generator:
gen.send(): update a value within the generatorgen.close(): stop the generator, useful for infinite generatorsgen.throw(): throw an error, which can be handled or not by the generatordef my_generator_1(x):
    while True:
        i = yield x
        print("My update from the world", i)
        if i is not None:
            x = i
        x += 1
gen = my_generator_1(5)
gen.send(6)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[9], line 1
----> 1 gen.send(6)
TypeError: can't send non-None value to a just-started generator
next(gen)
>>> 5
next(gen)
>>>  My update from the world None
>>> 6
gen.send(10) # Send used here !
>>> My update from the world 10
>>> 11
Move from somewhere to \(10\) and apply the +1
What happened ?
The send(10) replaces i by 10 AND call the generator for one loop:
- `x = i`
- `x += 1 # 11`
- New round of the loop
- `i = yield x`: the generator stop here and return `x` which was `11`
Next, you can continue using the generator:
next(gen)
>>>    My update from the world None
>>>    12
Here, i in the print() is None, as there were no send() message, so i was not updated.
When calling next(gen), it is similar to gen.send(None).
Stop the generator.
gen.close()
gen
>>> <generator object my_generator_1 at 0x7f4eec0e0f20>
next(gen)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
Cell In[16], line 1
----> 1 next(gen)
StopIteration:
Usefulness of stop ? I do not know. To stop a loop without using a break ?
def my_generator_infinite(x):
    while True:
        x += 1
        yield x
Usage without close():
gen = my_generator_infinite(10)
for i in gen:
    print(i)
    if i > 15:
        break
print("=== break ===")
for i in gen:
    print(i)
    if i > 20:
        break
>>>    11
>>>    12
>>>    13
>>>    14
>>>    15
>>>    16
>>>    === break ===
>>>    17
>>>    18
>>>    19
>>>    20
>>>    21
Usage with close()
gen = my_generator_infinite(10)
for i in gen:
    print(i)
    if i > 15:
        gen.close()
print("=== break ===")
# We do not enter the loop, because the generator does not return anything.
for i in gen:
    print(i)
    if i > 20:
        break
>>>    11
>>>    12
>>>    13
>>>    14
>>>    15
>>>    16
>>>    === break ===
For errors …
gen = my_generator_infinite(10)
for i in gen:
    print(i)
    if i > 15:
        gen.throw(ValueError("Please stop the loop"))
>>>    11
>>>    12
>>>    13
>>>    14
>>>    15
>>>    16
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[20], line 6
      4 print(i)
      5 if i > 15:
----> 6     gen.throw(ValueError("Please stop the loop"))
Cell In[17], line 4, in my_generator_infinite(x)
      2 while True:
      3     x += 1
----> 4     yield x
ValueError: Please stop the loop
This has the effect of stopping the generator
next(gen)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
Cell In[21], line 1
----> 1 next(gen)
StopIteration:
def my_generator_error_handling(x):
    while True:
        x += 1
        try:
            yield x
        except ValueError as e:
            print("Error message:", e)
            pass
gen = my_generator_error_handling(10)
for i in gen:
    print(i)
    if i > 15:
        gen.throw(ValueError("Please stop the loop"))
    if i > 20:
        # To stop
        gen.close()
>>>    11
>>>    12
>>>    13
>>>    14
>>>    15
>>>    16
>>>    Error message: Please stop the loop
>>>    18
>>>    Error message: Please stop the loop
>>>    20
>>>    Error message: Please stop the loop
>>>    22
>>>    Error message: Please stop the loop
Check Real Python - Introduction to python generators for an in-depth introduction
>> You can subscribe to my mailing list here for a monthly update. <<