Skip to content

Commit e910ec5

Browse files
committed
draft of coroutine examples
1 parent dfb3c3b commit e910ec5

File tree

13 files changed

+326
-25
lines changed

13 files changed

+326
-25
lines changed

concurrency/charfinder/charfinder.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ def describe(self, char):
186186
return CharDescription(code_str, char, name)
187187

188188
def find_descriptions(self, query, start=0, stop=None):
189-
for char in self.find_chars(query, start, stop):
189+
for char in self.find_chars(query, start, stop).items:
190190
yield self.describe(char)
191191

192192
def get_descriptions(self, chars):
@@ -197,7 +197,7 @@ def describe_str(self, char):
197197
return '{:7}\t{}\t{}'.format(*self.describe(char))
198198

199199
def find_description_strs(self, query, start=0, stop=None):
200-
for char in self.find_chars(query, start, stop):
200+
for char in self.find_chars(query, start, stop).items:
201201
yield self.describe_str(char)
202202

203203
@staticmethod # not an instance method due to concurrency

control/adder/coroadder.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545

4646
def adder_coro(initial=0):
4747
total = initial
48-
num_terms = 0
48+
count = 0
4949
while True:
5050
try:
5151
term = yield total
@@ -54,8 +54,8 @@ def adder_coro(initial=0):
5454
if term is None:
5555
break
5656
total += term
57-
num_terms += 1
58-
return Result(total, num_terms, total/num_terms)
57+
count += 1
58+
return Result(total, count, total/count)
5959

6060

6161
def prompt():

control/adder/coroadder0.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
44
>>> adder = adder_coro()
55
>>> next(adder)
6+
0
67
>>> adder.send(10)
8+
10
79
>>> adder.send(20)
10+
30
811
>>> adder.send(30)
12+
60
913
>>> adder.close()
1014
-> total: 60 terms: 3 average: 20.0
1115
@@ -14,7 +18,9 @@
1418
1519
>>> adder = adder_coro()
1620
>>> next(adder)
21+
0
1722
>>> adder.send(10)
23+
10
1824
>>> adder.send('spam')
1925
Traceback (most recent call last):
2026
...
@@ -25,13 +31,13 @@
2531

2632
def adder_coro(initial=0):
2733
total = initial
28-
num_terms = 0
34+
count = 0
2935
try:
3036
while True:
31-
term = yield
37+
term = yield total
3238
total += term
33-
num_terms += 1
39+
count += 1
3440
except GeneratorExit:
35-
average = total / num_terms
41+
average = total / count
3642
msg = '-> total: {} terms: {} average: {}'
37-
print(msg.format(total, num_terms, average))
43+
print(msg.format(total, count, average))

control/coro_exc_demo.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""
2+
Coroutine closing demonstration::
3+
4+
# BEGIN DEMO_CORO_EXC_1
5+
>>> exc_coro = demo_exc_handling()
6+
>>> next(exc_coro)
7+
-> coroutine started
8+
>>> exc_coro.send(11)
9+
-> coroutine received: 11
10+
>>> exc_coro.send(22)
11+
-> coroutine received: 22
12+
>>> exc_coro.close()
13+
>>> from inspect import getgeneratorstate
14+
>>> getgeneratorstate(exc_coro)
15+
'GEN_CLOSED'
16+
17+
# END DEMO_CORO_EXC_1
18+
19+
Coroutine handling exception::
20+
21+
# BEGIN DEMO_CORO_EXC_2
22+
>>> exc_coro = demo_exc_handling()
23+
>>> next(exc_coro)
24+
-> coroutine started
25+
>>> exc_coro.send(11)
26+
-> coroutine received: 11
27+
>>> exc_coro.throw(DemoException)
28+
*** DemoException handled. Continuing...
29+
>>> getgeneratorstate(exc_coro)
30+
'GEN_SUSPENDED'
31+
32+
# END DEMO_CORO_EXC_2
33+
34+
Coroutine not handling exception::
35+
36+
# BEGIN DEMO_CORO_EXC_3
37+
>>> exc_coro = demo_exc_handling()
38+
>>> next(exc_coro)
39+
-> coroutine started
40+
>>> exc_coro.send(11)
41+
-> coroutine received: 11
42+
>>> exc_coro.throw(ZeroDivisionError)
43+
Traceback (most recent call last):
44+
...
45+
ZeroDivisionError
46+
>>> getgeneratorstate(exc_coro)
47+
'GEN_CLOSED'
48+
49+
# END DEMO_CORO_EXC_3
50+
"""
51+
52+
# BEGIN EX_CORO_EXC
53+
class DemoException(Exception):
54+
"""An exception type for the demonstration."""
55+
56+
def demo_exc_handling():
57+
print('-> coroutine started')
58+
while True:
59+
try:
60+
x = yield
61+
except DemoException: # <1>
62+
print('*** DemoException handled. Continuing...')
63+
else: # <2>
64+
print('-> coroutine received: {!r}'.format(x))
65+
raise RuntimeError('This line should never run.') # <3>
66+
# END EX_CORO_EXC

control/coro_finally_demo.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
Second coroutine closing demonstration::
3+
4+
>>> fin_coro = demo_finally()
5+
>>> next(fin_coro)
6+
-> coroutine started
7+
>>> fin_coro.send(11)
8+
-> coroutine received: 11
9+
>>> fin_coro.send(22)
10+
-> coroutine received: 22
11+
>>> fin_coro.close()
12+
-> coroutine ending
13+
14+
15+
Second coroutine not handling exception::
16+
17+
>>> fin_coro = demo_finally()
18+
>>> next(fin_coro)
19+
-> coroutine started
20+
>>> fin_coro.send(11)
21+
-> coroutine received: 11
22+
>>> fin_coro.throw(ZeroDivisionError) # doctest: +SKIP
23+
-> coroutine ending
24+
Traceback (most recent call last):
25+
File "<stdin>", line 1, in <module>
26+
File "coro_exception_demos.py", line 109, in demo_finally
27+
print('-> coroutine received: {!r}'.format(x))
28+
ZeroDivisionError
29+
30+
31+
The last test above must be skipped because the output '-> coroutine ending'
32+
is not detected by doctest, which raises a false error. However, if you
33+
run this file as shown below, you'll see that output "leak" into standard
34+
output::
35+
36+
37+
$ python3 -m doctest coro_exception_demos.py
38+
-> coroutine ending
39+
40+
"""
41+
42+
43+
# BEGIN EX_CORO_FINALLY
44+
class DemoException(Exception):
45+
"""An exception type for the demonstration."""
46+
47+
48+
def demo_finally():
49+
print('-> coroutine started')
50+
try:
51+
while True:
52+
try:
53+
x = yield
54+
except DemoException:
55+
print('*** DemoException handled. Continuing...')
56+
else:
57+
print('-> coroutine received: {!r}'.format(x))
58+
finally:
59+
print('-> coroutine ending')
60+
61+
# END EX_CORO_FINALLY

control/coroaverager.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""
2+
Closing a generator raises ``GeneratorExit`` at the pending ``yield``
3+
4+
>>> coro_avg = averager()
5+
>>> next(coro_avg)
6+
0.0
7+
>>> coro_avg.send(10)
8+
10.0
9+
>>> coro_avg.send(20)
10+
15.0
11+
>>> coro_avg.send(30)
12+
20.0
13+
>>> coro_avg.close()
14+
-> total: 60.0 average: 20.0 terms: 3
15+
16+
17+
Other exceptions propagate to the caller:
18+
19+
>>> coro_avg = averager()
20+
>>> next(coro_avg)
21+
0.0
22+
>>> coro_avg.send(10)
23+
10.0
24+
>>> coro_avg.send('spam')
25+
Traceback (most recent call last):
26+
...
27+
TypeError: unsupported operand type(s) for +=: 'float' and 'str'
28+
29+
30+
"""
31+
32+
# BEGIN CORO_AVERAGER
33+
def averager():
34+
total = average = 0.0
35+
count = 0
36+
try:
37+
while True:
38+
term = yield average
39+
total += term
40+
count += 1
41+
average = total/count
42+
except GeneratorExit:
43+
msg = '-> total: {} average: {} terms: {}'
44+
print(msg.format(total, average, count))
45+
# END CORO_AVERAGER

control/coroaverager0.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# BEGIN CORO_AVERAGER
2+
"""
3+
A coroutine to compute a running average
4+
5+
>>> coro_avg = averager() # <1>
6+
>>> next(coro_avg) # <2>
7+
0.0
8+
>>> coro_avg.send(10) # <3>
9+
10.0
10+
>>> coro_avg.send(30)
11+
20.0
12+
>>> coro_avg.send(5)
13+
15.0
14+
15+
"""
16+
17+
def averager():
18+
total = average = 0.0
19+
count = 0
20+
while True:
21+
term = yield average # <4>
22+
total += term
23+
count += 1
24+
average = total/count
25+
# END CORO_AVERAGER

control/coroaverager1.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# BEGIN DECORATED_AVERAGER
2+
"""
3+
A coroutine to compute a running average
4+
5+
>>> coro_avg = averager() # <1>
6+
>>> from inspect import getgeneratorstate
7+
>>> getgeneratorstate(coro_avg) # <2>
8+
'GEN_SUSPENDED'
9+
>>> coro_avg.send(10) # <3>
10+
10.0
11+
>>> coro_avg.send(30)
12+
20.0
13+
>>> coro_avg.send(5)
14+
15.0
15+
16+
"""
17+
18+
from coroutil import coroutine # <4>
19+
20+
@coroutine # <5>
21+
def averager(): # <6>
22+
total = average = 0.0
23+
count = 0
24+
while True:
25+
term = yield average
26+
total += term
27+
count += 1
28+
average = total/count
29+
# END DECORATED_AVERAGER

control/coroaverager2.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""
2+
A coroutine to compute a running average
3+
4+
>>> coro_avg = averager() # <1>
5+
>>> from inspect import getgeneratorstate
6+
>>> getgeneratorstate(coro_avg) # <2>
7+
'GEN_SUSPENDED'
8+
>>> coro_avg.send(10) # <3>
9+
10.0
10+
>>> coro_avg.send(30)
11+
20.0
12+
>>> coro_avg.send(5)
13+
15.0
14+
15+
"""
16+
17+
from collections import namedtuple
18+
19+
Result = namedtuple('Result', 'count total average')
20+
21+
def averager(): # <6>
22+
total = average = 0.0
23+
count = 0
24+
try:
25+
while True:
26+
term = yield average
27+
total += term
28+
count += 1
29+
average = total/count
30+
finally:
31+
return Result(count, total, average)

0 commit comments

Comments
 (0)