draft of coroutine examples
This commit is contained in:
parent
dfb3c3b895
commit
e910ec5458
@ -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
|
||||
|
@ -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():
|
||||
|
@ -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
66
control/coro_exc_demo.py
Normal 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
|
61
control/coro_finally_demo.py
Normal file
61
control/coro_finally_demo.py
Normal 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
45
control/coroaverager.py
Normal 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
25
control/coroaverager0.py
Normal 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
29
control/coroaverager1.py
Normal 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
31
control/coroaverager2.py
Normal 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
12
control/coroutil.py
Normal 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
21
control/flatten.py
Normal 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
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user