draft of coroutine examples

This commit is contained in:
Luciano Ramalho 2015-02-17 10:07:07 -02:00
parent dfb3c3b895
commit e910ec5458
13 changed files with 326 additions and 25 deletions

View File

@ -186,7 +186,7 @@ class UnicodeNameIndex:
return CharDescription(code_str, char, name)
def find_descriptions(self, query, start=0, stop=None):
for char in self.find_chars(query, start, stop):
for char in self.find_chars(query, start, stop).items:
yield self.describe(char)
def get_descriptions(self, chars):
@ -197,7 +197,7 @@ class UnicodeNameIndex:
return '{:7}\t{}\t{}'.format(*self.describe(char))
def find_description_strs(self, query, start=0, stop=None):
for char in self.find_chars(query, start, stop):
for char in self.find_chars(query, start, stop).items:
yield self.describe_str(char)
@staticmethod # not an instance method due to concurrency

View File

@ -45,7 +45,7 @@ Result = collections.namedtuple('Result', 'sum terms average')
def adder_coro(initial=0):
total = initial
num_terms = 0
count = 0
while True:
try:
term = yield total
@ -54,8 +54,8 @@ def adder_coro(initial=0):
if term is None:
break
total += term
num_terms += 1
return Result(total, num_terms, total/num_terms)
count += 1
return Result(total, count, total/count)
def prompt():

View File

@ -3,9 +3,13 @@ Closing a generator raises ``GeneratorExit`` at the pending ``yield``
>>> adder = adder_coro()
>>> next(adder)
0
>>> adder.send(10)
10
>>> adder.send(20)
30
>>> adder.send(30)
60
>>> adder.close()
-> total: 60 terms: 3 average: 20.0
@ -14,7 +18,9 @@ Other exceptions propagate to the caller:
>>> adder = adder_coro()
>>> next(adder)
0
>>> adder.send(10)
10
>>> adder.send('spam')
Traceback (most recent call last):
...
@ -25,13 +31,13 @@ Other exceptions propagate to the caller:
def adder_coro(initial=0):
total = initial
num_terms = 0
count = 0
try:
while True:
term = yield
term = yield total
total += term
num_terms += 1
count += 1
except GeneratorExit:
average = total / num_terms
average = total / count
msg = '-> total: {} terms: {} average: {}'
print(msg.format(total, num_terms, average))
print(msg.format(total, count, average))

66
control/coro_exc_demo.py Normal file
View File

@ -0,0 +1,66 @@
"""
Coroutine closing demonstration::
# BEGIN DEMO_CORO_EXC_1
>>> exc_coro = demo_exc_handling()
>>> next(exc_coro)
-> coroutine started
>>> exc_coro.send(11)
-> coroutine received: 11
>>> exc_coro.send(22)
-> coroutine received: 22
>>> exc_coro.close()
>>> from inspect import getgeneratorstate
>>> getgeneratorstate(exc_coro)
'GEN_CLOSED'
# END DEMO_CORO_EXC_1
Coroutine handling exception::
# BEGIN DEMO_CORO_EXC_2
>>> exc_coro = demo_exc_handling()
>>> next(exc_coro)
-> coroutine started
>>> exc_coro.send(11)
-> coroutine received: 11
>>> exc_coro.throw(DemoException)
*** DemoException handled. Continuing...
>>> getgeneratorstate(exc_coro)
'GEN_SUSPENDED'
# END DEMO_CORO_EXC_2
Coroutine not handling exception::
# BEGIN DEMO_CORO_EXC_3
>>> exc_coro = demo_exc_handling()
>>> next(exc_coro)
-> coroutine started
>>> exc_coro.send(11)
-> coroutine received: 11
>>> exc_coro.throw(ZeroDivisionError)
Traceback (most recent call last):
...
ZeroDivisionError
>>> getgeneratorstate(exc_coro)
'GEN_CLOSED'
# END DEMO_CORO_EXC_3
"""
# BEGIN EX_CORO_EXC
class DemoException(Exception):
"""An exception type for the demonstration."""
def demo_exc_handling():
print('-> coroutine started')
while True:
try:
x = yield
except DemoException: # <1>
print('*** DemoException handled. Continuing...')
else: # <2>
print('-> coroutine received: {!r}'.format(x))
raise RuntimeError('This line should never run.') # <3>
# END EX_CORO_EXC

View File

@ -0,0 +1,61 @@
"""
Second coroutine closing demonstration::
>>> fin_coro = demo_finally()
>>> next(fin_coro)
-> coroutine started
>>> fin_coro.send(11)
-> coroutine received: 11
>>> fin_coro.send(22)
-> coroutine received: 22
>>> fin_coro.close()
-> coroutine ending
Second coroutine not handling exception::
>>> fin_coro = demo_finally()
>>> next(fin_coro)
-> coroutine started
>>> fin_coro.send(11)
-> coroutine received: 11
>>> fin_coro.throw(ZeroDivisionError) # doctest: +SKIP
-> coroutine ending
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "coro_exception_demos.py", line 109, in demo_finally
print('-> coroutine received: {!r}'.format(x))
ZeroDivisionError
The last test above must be skipped because the output '-> coroutine ending'
is not detected by doctest, which raises a false error. However, if you
run this file as shown below, you'll see that output "leak" into standard
output::
$ python3 -m doctest coro_exception_demos.py
-> coroutine ending
"""
# BEGIN EX_CORO_FINALLY
class DemoException(Exception):
"""An exception type for the demonstration."""
def demo_finally():
print('-> coroutine started')
try:
while True:
try:
x = yield
except DemoException:
print('*** DemoException handled. Continuing...')
else:
print('-> coroutine received: {!r}'.format(x))
finally:
print('-> coroutine ending')
# END EX_CORO_FINALLY

45
control/coroaverager.py Normal file
View File

@ -0,0 +1,45 @@
"""
Closing a generator raises ``GeneratorExit`` at the pending ``yield``
>>> coro_avg = averager()
>>> next(coro_avg)
0.0
>>> coro_avg.send(10)
10.0
>>> coro_avg.send(20)
15.0
>>> coro_avg.send(30)
20.0
>>> coro_avg.close()
-> total: 60.0 average: 20.0 terms: 3
Other exceptions propagate to the caller:
>>> coro_avg = averager()
>>> next(coro_avg)
0.0
>>> coro_avg.send(10)
10.0
>>> coro_avg.send('spam')
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +=: 'float' and 'str'
"""
# BEGIN CORO_AVERAGER
def averager():
total = average = 0.0
count = 0
try:
while True:
term = yield average
total += term
count += 1
average = total/count
except GeneratorExit:
msg = '-> total: {} average: {} terms: {}'
print(msg.format(total, average, count))
# END CORO_AVERAGER

25
control/coroaverager0.py Normal file
View File

@ -0,0 +1,25 @@
# BEGIN CORO_AVERAGER
"""
A coroutine to compute a running average
>>> coro_avg = averager() # <1>
>>> next(coro_avg) # <2>
0.0
>>> coro_avg.send(10) # <3>
10.0
>>> coro_avg.send(30)
20.0
>>> coro_avg.send(5)
15.0
"""
def averager():
total = average = 0.0
count = 0
while True:
term = yield average # <4>
total += term
count += 1
average = total/count
# END CORO_AVERAGER

29
control/coroaverager1.py Normal file
View File

@ -0,0 +1,29 @@
# BEGIN DECORATED_AVERAGER
"""
A coroutine to compute a running average
>>> coro_avg = averager() # <1>
>>> from inspect import getgeneratorstate
>>> getgeneratorstate(coro_avg) # <2>
'GEN_SUSPENDED'
>>> coro_avg.send(10) # <3>
10.0
>>> coro_avg.send(30)
20.0
>>> coro_avg.send(5)
15.0
"""
from coroutil import coroutine # <4>
@coroutine # <5>
def averager(): # <6>
total = average = 0.0
count = 0
while True:
term = yield average
total += term
count += 1
average = total/count
# END DECORATED_AVERAGER

31
control/coroaverager2.py Normal file
View File

@ -0,0 +1,31 @@
"""
A coroutine to compute a running average
>>> coro_avg = averager() # <1>
>>> from inspect import getgeneratorstate
>>> getgeneratorstate(coro_avg) # <2>
'GEN_SUSPENDED'
>>> coro_avg.send(10) # <3>
10.0
>>> coro_avg.send(30)
20.0
>>> coro_avg.send(5)
15.0
"""
from collections import namedtuple
Result = namedtuple('Result', 'count total average')
def averager(): # <6>
total = average = 0.0
count = 0
try:
while True:
term = yield average
total += term
count += 1
average = total/count
finally:
return Result(count, total, average)

12
control/coroutil.py Normal file
View File

@ -0,0 +1,12 @@
# BEGIN CORO_DECO
from functools import wraps
def coroutine(func):
"""Decorator: primes `func` by advancing to first `yield`"""
@wraps(func)
def primer(*args,**kwargs): # <1>
gen = func(*args,**kwargs) # <2>
next(gen) # <3>
return gen # <4>
return primer
# END CORO_DECO

21
control/flatten.py Normal file
View File

@ -0,0 +1,21 @@
"""
>>> items = [1, 2, [3, 4, [5, 6], 7], 8]
>>> flatten(items)
<generator object flatten at 0x73bd9c>
>>> list(flatten(items))
[1, 2, 3, 4, 5, 6, 7, 8]
>>> mixed_bag = [1, 'spam', 2, [3, 'eggs', 4], {'x': 1, 'y': 2}]
>>> list(flatten(mixed_bag))
[1, 'spam', 2, 3, 'eggs', 4, 'y', 'x']
"""
from collections import Iterable
def flatten(items):
for x in items:
if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
yield from flatten(x)
else:
yield x

View File

@ -41,6 +41,10 @@ This exposes the context manager operation::
# END MIRROR_GEN_DEMO_2
The context manager can handle and "swallow" exceptions.
The following test does not pass under doctest (a
ZeroDivisionError is reported by doctest) but passes
if executed by hand in the Python 3 console (the exception
is handled by the context manager):
# BEGIN MIRROR_GEN_DEMO_3
@ -52,6 +56,9 @@ The context manager can handle and "swallow" exceptions.
...
ytpmuD ytpmuH
Please DO NOT divide by zero!
# END MIRROR_GEN_DEMO_3
>>> with looking_glass():
... print('Humpty Dumpty')
... x = no_such_name # <1>
@ -61,36 +68,34 @@ The context manager can handle and "swallow" exceptions.
...
NameError: name 'no_such_name' is not defined
# END MIRROR_GEN_DEMO_3
"""
# BEGIN MIRROR_GEN_EX
# BEGIN MIRROR_GEN_EXC
import contextlib
@contextlib.contextmanager # <1>
@contextlib.contextmanager
def looking_glass():
import sys
original_write = sys.stdout.write # <2>
original_write = sys.stdout.write
def reverse_write(text): # <3>
def reverse_write(text):
original_write(text[::-1])
sys.stdout.write = reverse_write # <4>
msg = ''
sys.stdout.write = reverse_write
msg = '' # <1>
try:
yield 'JABBERWOCKY' # <5>
except ZeroDivisionError: # <6>
msg = 'Please DO NOT divide by zero!' # <7>
except:
raise # <8>
yield 'JABBERWOCKY'
except ZeroDivisionError: # <2>
msg = 'Please DO NOT divide by zero!'
finally:
sys.stdout.write = original_write # <9>
sys.stdout.write = original_write # <3>
if msg:
print(msg) # <10>
print(msg) # <4>
# END MIRROR_GEN_EX
# END MIRROR_GEN_EXC