Initial commit
This commit is contained in:
153
Exercises/ex8_4.md
Normal file
153
Exercises/ex8_4.md
Normal file
@@ -0,0 +1,153 @@
|
||||
\[ [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
|
||||
|
||||
. This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)
|
||||
Reference in New Issue
Block a user