The iterator itself may not turn to the end due to break etc., but there are rare cases where you want to turn the iteration inside the generator to the end.
As an example, the following range_printsum adds the process of printing the sum of the range to the range function.
gen.py
def range_printsum(*args, **kwargs):
sum_ = 0
for i in range(*args, **kwargs):
sum_ += i
yield i
print(sum_)
In this generator implementation, if the iteration is stopped in the middle, what is stored in sum_ is the total number actually yield, and sum_ is not output in the first place.
>>> from gen import range_printsum
>>> for i in range_printsum(11):
... pass
...
55
>>> for i in range_printsum(11):
... if i > 5:
... break
...
>>>
Then capture Generator Exit. This is sent when the generator is closed (when the generator's close () is called). This can be captured even if the loop such as for is interrupted.
After capturing GeneratorExit, modify range_printsum so that it does not return a value and only adds.
gen.py
def range_printsum(*args, **kwargs):
sum_ = 0
stop_yield = False
for i in range(*args, **kwargs):
sum_ += i
if not stop_yield:
try:
yield i
except GeneratorExit:
stop_yield = True
print(sum_)
This time it works as expected.
>>> from gen import range_printsum
>>> for i in range_printsum(11):
... pass
...
55
>>> for i in range_printsum(11):
... if i > 5:
... break
...
55
>>>
It works even if it is interrupted like this.
>>> try:
... for i in range_printsum(11):
... if i > 5:
... raise IndexError
... except IndexError:
... pass
...
55
>>>
A caveat to the generator specifications is that once a generator is closed, it must not generate a value. If you yield any value after capturing GeneratorExit, you will get the error RuntimeError: generator ignored GeneratorExit.
Recommended Posts