# 协程

``````>>> for i in range(3):
...     print(i)
...
1
2
>>> for line in open('exchange_rates_v1.py'):
...     print(line, end='')
...
#!/usr/bin/env python3
import itertools
import time
import urllib.request
…
``````

``````class MyIterator(object):
def __init__(self, xs):
self.xs = xs

def __iter__(self):
return self

def __next__(self):
if self.xs:
return self.xs.pop(0)
else:
raise StopIteration

for i in MyIterator([0, 1, 2]):
print(i)
``````

``````1
2
3
``````

``````itrtr = MyIterator([3, 4, 5, 6])

print(next(itrtr))
print(next(itrtr))
print(next(itrtr))
print(next(itrtr))

print(next(itrtr))
``````

``````3
4
5
6
Traceback (most recent call last):
File "iteration.py", line 32, in <module>
print(next(itrtr))
File "iteration.py", line 19, in __next__
raise StopIteration
StopIteration
``````

``````def mygenerator(n):
while n:
n -= 1
yield n

if __name__ == '__main__':
for i in mygenerator(3):
print(i)
``````

``````2
1
0
``````

``````>>> from generators import mygenerator
>>> mygenerator(5)
<generator object mygenerator at 0x101267b48>
``````

``````>>> g = mygenerator(2)
>>> next(g)
1
>>> next(g)
0
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
``````

• `yield()`： 用来暂停协程的执行
• `send()`： 用来向协程传递数据（以让协程继续执行）
• `close()`：用来关闭协程

``````def complain_about(substring):
try:
while True:
text = (yield)
if substring in text:
print('Oh no: I found a %s again!'
% (substring))
except GeneratorExit:
print('Ok, ok: I am quitting.')
``````

``````>>> from coroutines import complain_about
>>> next(c)
>>> c.send('Test data')
>>> c.send('Some more random text')
>>> c.send('Test data with Ruby somewhere in it')
Oh no: I found a Ruby again!
>>> c.send('Stop complaining about Ruby or else!')
Oh no: I found a Ruby again!
>>> c.close()
Ok, ok: I am quitting.
``````

``````>>> c.send('Test data')
>>> c.send('Some more random text')
>>> c.send('Test data with Ruby somewhere in it')
Oh no: I found a Ruby again!
``````

``````>>> c.close()
Ok, ok: I am quitting.
``````

``````>>> def complain_about2(substring):
...     while True:
...         text = (yield)
...         if substring in text:
...             print('Oh no: I found a %s again!'
...                   % (substring))
...
>>> next(c)
>>> c.close()
>>> c.send('This will crash')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> next(c)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
``````

``````>>> def coroutine(fn):
...     def wrapper(*args, **kwargs):
...         c = fn(*args, **kwargs)
...         next(c)
...         return c
...     return wrapper
...
>>> @coroutine
...     while True:
...         text = (yield)
...         if substring in text:
...             print('Oh no: I found a %s again!'
...                   % (substring))
...
>>> c.send('Test data with JavaScript somewhere in it')
Oh no: I found a JavaScript again!
>>> c.close()
``````

# 一个异步实例

``````\$ curl -sO http://www.gutenberg.org/cache/epub/2600/pg2600.txt
\$ wc pg2600.txt
65007  566320 3291648 pg2600.txt
``````

``````\$ time (grep -io love pg2600.txt | wc -l)
677
(grep -io love pg2600.txt) 0.11s user 0.00s system 98% cpu 0.116 total
``````

``````def coroutine(fn):
def wrapper(*args, **kwargs):
c = fn(*args, **kwargs)
next(c)
return c
return wrapper

def cat(f, case_insensitive, child):
if case_insensitive:
line_processor = lambda l: l.lower()
else:
line_processor = lambda l: l

for line in f:
child.send(line_processor(line))

@coroutine
def grep(substring, case_insensitive, child):
if case_insensitive:
substring = substring.lower()
while True:
text = (yield)
child.send(text.count(substring))

@coroutine
def count(substring):
n = 0
try:
while True:
n += (yield)
except GeneratorExit:
print(substring, n)

if __name__ == '__main__':
import argparse

parser = argparse.ArgumentParser()
dest='case_insensitive')

args = parser.parse_args()

cat(args.infile, args.case_insensitive,
grep(args.pattern, args.case_insensitive,
count(args.pattern)))
``````

``````\$ time python3.5 grep.py -i love pg2600.txt
love 677
python3.5 grep.py -i love pg2600.txt  0.09s user 0.01s system 97% cpu 0.097 total
``````

• 逐行读取文件（通过`cat`函数）
• 统计每行中`substring`的出现次数（`grep`协程）
• 求和并打印数据（`count`协程）

`child.send(line)`）。如果匹配是大小写不敏感的，不需要进行转换；如果大小写敏感，则都转化为小写。

`grep`命令是我们的第一个协程。这里，进入一个无限循环，持续获取数据（`text = (yield)`），统计`substring``text`中的出现次数，，将次数发送给写一个协程（即`count`）：`child.send(text.count(substring)))`

`count`协程用总次数`n`，从`grep`获取数据，对总次数进行求和，`n += (yield)`。它捕获发送给各个协程关闭时的`GeneratorExit`异常（在我们的例子中，到达文件最后就会出现异常），以判断何时打印这个`substring``n`

``````def coroutine(fn):
def wrapper(*args, **kwargs):
c = fn(*args, **kwargs)
next(c)
return c
return wrapper

def cat(f, case_insensitive, child):
if case_insensitive:
line_processor = lambda l: l.lower()
else:
line_processor = lambda l: l

for line in f:
child.send(line_processor(line))

@coroutine
def grep(substring, case_insensitive, child):
if case_insensitive:
substring = substring.lower()
while True:
text = (yield)
child.send(text.count(substring))

@coroutine
def count(substring):
n = 0
try:
while True:
n += (yield)
except GeneratorExit:
print(substring, n)

@coroutine
def fanout(children):
while True:
data = (yield)
for child in children:
child.send(data)

if __name__ == '__main__':
import argparse

parser = argparse.ArgumentParser()

args = parser.parse_args()

cat(args.infile, args.case_insensitive,
fanout([grep(p, args.case_insensitive,
count(p)) for p in args.patterns]))
``````

``````\$ time python3.5 mgrep.py -i love hate hope pg2600.txt
hate 103
love 677
hope 158
python3.5 mgrep.py -i love hate hope pg2600.txt  0.16s user 0.01s system 98% cpu 0.166 total
``````

# 总结

Python从1.5.2版本之后引入了`asyncore``asynchat`模块，开始支持异步编程。2.5版本引入了`yield`，可以向协程传递数据，简化了代码、加强了性能。Python 3.4 引入了一个新的库进行异步I/O，称作`asyncio`

Python 3.5通过`async def``await`，引入了真正的协程类型。感兴趣的读者可以继续研究Python的新扩展。一句警告：异步编程是一个强大的工具，可以极大地提高I/O密集型代码的性能。但是异步编程也是存在问题的，而且还相当复杂。