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. <<