draft of coroutine examples
This commit is contained in:
@@ -186,7 +186,7 @@ class UnicodeNameIndex:
|
|||||||
return CharDescription(code_str, char, name)
|
return CharDescription(code_str, char, name)
|
||||||
|
|
||||||
def find_descriptions(self, query, start=0, stop=None):
|
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)
|
yield self.describe(char)
|
||||||
|
|
||||||
def get_descriptions(self, chars):
|
def get_descriptions(self, chars):
|
||||||
@@ -197,7 +197,7 @@ class UnicodeNameIndex:
|
|||||||
return '{:7}\t{}\t{}'.format(*self.describe(char))
|
return '{:7}\t{}\t{}'.format(*self.describe(char))
|
||||||
|
|
||||||
def find_description_strs(self, query, start=0, stop=None):
|
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)
|
yield self.describe_str(char)
|
||||||
|
|
||||||
@staticmethod # not an instance method due to concurrency
|
@staticmethod # not an instance method due to concurrency
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ Result = collections.namedtuple('Result', 'sum terms average')
|
|||||||
|
|
||||||
def adder_coro(initial=0):
|
def adder_coro(initial=0):
|
||||||
total = initial
|
total = initial
|
||||||
num_terms = 0
|
count = 0
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
term = yield total
|
term = yield total
|
||||||
@@ -54,8 +54,8 @@ def adder_coro(initial=0):
|
|||||||
if term is None:
|
if term is None:
|
||||||
break
|
break
|
||||||
total += term
|
total += term
|
||||||
num_terms += 1
|
count += 1
|
||||||
return Result(total, num_terms, total/num_terms)
|
return Result(total, count, total/count)
|
||||||
|
|
||||||
|
|
||||||
def prompt():
|
def prompt():
|
||||||
|
|||||||
@@ -3,9 +3,13 @@ Closing a generator raises ``GeneratorExit`` at the pending ``yield``
|
|||||||
|
|
||||||
>>> adder = adder_coro()
|
>>> adder = adder_coro()
|
||||||
>>> next(adder)
|
>>> next(adder)
|
||||||
|
0
|
||||||
>>> adder.send(10)
|
>>> adder.send(10)
|
||||||
|
10
|
||||||
>>> adder.send(20)
|
>>> adder.send(20)
|
||||||
|
30
|
||||||
>>> adder.send(30)
|
>>> adder.send(30)
|
||||||
|
60
|
||||||
>>> adder.close()
|
>>> adder.close()
|
||||||
-> total: 60 terms: 3 average: 20.0
|
-> total: 60 terms: 3 average: 20.0
|
||||||
|
|
||||||
@@ -14,7 +18,9 @@ Other exceptions propagate to the caller:
|
|||||||
|
|
||||||
>>> adder = adder_coro()
|
>>> adder = adder_coro()
|
||||||
>>> next(adder)
|
>>> next(adder)
|
||||||
|
0
|
||||||
>>> adder.send(10)
|
>>> adder.send(10)
|
||||||
|
10
|
||||||
>>> adder.send('spam')
|
>>> adder.send('spam')
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
@@ -25,13 +31,13 @@ Other exceptions propagate to the caller:
|
|||||||
|
|
||||||
def adder_coro(initial=0):
|
def adder_coro(initial=0):
|
||||||
total = initial
|
total = initial
|
||||||
num_terms = 0
|
count = 0
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
term = yield
|
term = yield total
|
||||||
total += term
|
total += term
|
||||||
num_terms += 1
|
count += 1
|
||||||
except GeneratorExit:
|
except GeneratorExit:
|
||||||
average = total / num_terms
|
average = total / count
|
||||||
msg = '-> total: {} terms: {} average: {}'
|
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
|
# END MIRROR_GEN_DEMO_2
|
||||||
|
|
||||||
The context manager can handle and "swallow" exceptions.
|
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
|
# BEGIN MIRROR_GEN_DEMO_3
|
||||||
|
|
||||||
@@ -52,6 +56,9 @@ The context manager can handle and "swallow" exceptions.
|
|||||||
...
|
...
|
||||||
ytpmuD ytpmuH
|
ytpmuD ytpmuH
|
||||||
Please DO NOT divide by zero!
|
Please DO NOT divide by zero!
|
||||||
|
|
||||||
|
# END MIRROR_GEN_DEMO_3
|
||||||
|
|
||||||
>>> with looking_glass():
|
>>> with looking_glass():
|
||||||
... print('Humpty Dumpty')
|
... print('Humpty Dumpty')
|
||||||
... x = no_such_name # <1>
|
... 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
|
NameError: name 'no_such_name' is not defined
|
||||||
|
|
||||||
# END MIRROR_GEN_DEMO_3
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
# BEGIN MIRROR_GEN_EX
|
# BEGIN MIRROR_GEN_EXC
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager # <1>
|
@contextlib.contextmanager
|
||||||
def looking_glass():
|
def looking_glass():
|
||||||
import sys
|
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])
|
original_write(text[::-1])
|
||||||
|
|
||||||
sys.stdout.write = reverse_write # <4>
|
sys.stdout.write = reverse_write
|
||||||
msg = ''
|
msg = '' # <1>
|
||||||
try:
|
try:
|
||||||
yield 'JABBERWOCKY' # <5>
|
yield 'JABBERWOCKY'
|
||||||
except ZeroDivisionError: # <6>
|
except ZeroDivisionError: # <2>
|
||||||
msg = 'Please DO NOT divide by zero!' # <7>
|
msg = 'Please DO NOT divide by zero!'
|
||||||
except:
|
|
||||||
raise # <8>
|
|
||||||
finally:
|
finally:
|
||||||
sys.stdout.write = original_write # <9>
|
sys.stdout.write = original_write # <3>
|
||||||
if msg:
|
if msg:
|
||||||
print(msg) # <10>
|
print(msg) # <4>
|
||||||
|
|
||||||
|
|
||||||
# END MIRROR_GEN_EX
|
# END MIRROR_GEN_EXC
|
||||||
|
|||||||
Reference in New Issue
Block a user