python-mastery/Exercises/ex8_4.md

154 lines
4.2 KiB
Markdown
Raw Normal View History

2023-07-17 03:21:00 +02:00
\[ [Index](index.md) | [Exercise 8.3](ex8_3.md) | [Exercise 8.5](ex8_5.md) \]
# Exercise 8.4
*Objectives:*
- Managing what happens at the `yield` statements
*Files Modified:* `follow.py`, `cofollow.py`
## (a) Closing a Generator
A common question concerning generators is their lifetime and garbage
collection. For example, the `follow()` generator runs forever in
an infinite `while` loop. What happens if the iteration loop that's
driving it stops? Also, is there anyway to prematurely terminate the
generator?
Modify the `follow()` function so that all of the code is enclosed in
a `try-except` block like this:
```python
def follow(filename):
try:
with open(filename,'r') as f:
f.seek(0,os.SEEK_END)
while True:
line = f.readline()
if line == '':
time.sleep(0.1) # Sleep briefly to avoid busy wait
continue
yield line
except GeneratorExit:
print('Following Done')
```
Now, try a few experiments:
```python
>>> from follow import follow
>>> # Experiment: Garbage collection of a running generator
>>> f = follow('Data/stocklog.csv')
>>> next(f)
'"MO",70.29,"6/11/2007","09:30.09",-0.01,70.25,70.30,70.29,365314\n'
>>> del f
Following Done
>>> # Experiment: Closing a generator
>>> f = follow('Data/stocklog.csv')
>>> for line in f:
print(line,end='')
if 'IBM' in line:
f.close()
"VZ",42.91,"6/11/2007","09:34.28",-0.16,42.95,42.91,42.78,210151
"HPQ",45.76,"6/11/2007","09:34.29",0.06,45.80,45.76,45.59,257169
"GM",31.45,"6/11/2007","09:34.31",0.45,31.00,31.50,31.45,582429
...
"IBM",102.86,"6/11/2007","09:34.44",-0.21,102.87,102.86,102.77,147550
Following Done
>>> for line in f:
print(line, end='') # No output: generator is done
>>>
```
In these experiments you can see that a `GeneratorExit` exception is
raised when a generator is garbage-collected or explicitly closed via
its `close()` method.
One additional area of exploration is whether or not you can resume
iteration on a generator if you break out of a for-loop. For example,
try this:
```python
>>> f = follow('Data/stocklog.csv')
>>> for line in f:
print(line,end='')
if 'IBM' in line:
break
"CAT",78.36,"6/11/2007","09:37.19",-0.16,78.32,78.36,77.99,237714
"VZ",42.99,"6/11/2007","09:37.20",-0.08,42.95,42.99,42.78,268459
...
"IBM",102.91,"6/11/2007","09:37.31",-0.16,102.87,102.91,102.77,190859
>>> # Resume iteration
>>> for line in f:
print(line,end='')
if 'IBM' in line:
break
"AA",39.58,"6/11/2007","09:39.28",-0.08,39.67,39.58,39.31,243159
"HPQ",45.94,"6/11/2007","09:39.29",0.24,45.80,45.94,45.59,408919
...
"IBM",102.95,"6/11/2007","09:39.44",-0.12,102.87,102.95,102.77,225350
>>> del f
Following Done
>>>
```
In general, you can break out of running iteration and resume it later
if you need to. You just need to make sure the generator object isn't
forcefully closed or garbage collected somehow.
## (b) Raising Exceptions
In the file `cofollow.py`, you created a coroutine `printer()`. Modify the
code to catch and report exceptions like this:
```python
# cofollow.py
...
@consumer
def printer():
while True:
try:
item = yield
print(item)
except Exception as e:
print('ERROR: %r' % e)
```
Now, try an experiment:
```python
>>> from cofollow import printer
>>> p = printer()
>>> p.send('hello')
hello
>>> p.send(42)
42
>>> p.throw(ValueError('It failed'))
ERROR: ValueError('It failed',)
>>> try:
int('n/a')
except ValueError as e:
p.throw(e)
ERROR: ValueError("invalid literal for int() with base 10: 'n/a'",)
>>>
```
Notice how the running generator is not terminated by the exception. This
is merely allowing the `yield` statement to signal an error instead of
receiving a value.
\[ [Solution](soln8_4.md) | [Index](index.md) | [Exercise 8.3](ex8_3.md) | [Exercise 8.5](ex8_5.md) \]
----
`>>>` Advanced Python Mastery
`...` A course by [dabeaz](https://www.dabeaz.com)
`...` Copyright 2007-2023
![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)