在学习Python协程之前首先需要了解Python生成器的概念,而生成器又是一种特殊的迭代器,所以从迭代器开始学习。
首先了解可迭代对象(interable), 具体来说就是遵循了可迭代协议的对象,比如查看Python list内置类的源码可以发现它实现了iter()函数,所以list是一个可迭代对象,当然还有dict, str, set等等。
可迭代协议:含iter()方法。且可迭代对象中的iter()方法返回的是一个对应的迭代器。(如list对应的迭代器就是list_iterator)
而迭代器与可迭代对象不同的是,迭代器对象不仅需要实现iter()方法,它还需要实现next()方法,即迭代器协议,事实上任何实现了iter()和next()方法的类对象都可以是迭代器对象。
迭代器协议:
- 含iter()方法。且方法返回的Iterator对象本身
- 含next()方法,每当next()方法被调用,返回下一个值,直到没有值可以访问,这个时候会抛出stopinteration的异常。
此外迭代器含有两个基本的方法iter()和next(), iter()方法的作用是返回一个迭代器对象,当我们使用迭代器的next()方法显式获取元素的时候,迭代器对象会返回当前游标所指向的元素,并且向后移动游标位置,类似于C语言中的指针。如果没有元素可以获取,迭代器会抛出StopIteration异常,其实我们使用for…in…语句遍历迭代器元素的时候,for循环自动帮我们捕获了StopIteration异常。
问题来了,我们知道迭代器对象(例如:list)和可迭代对象都可以用for…in…语句调用,那么它们的区别是什么呢?举一个简单的栗子:
a = [1, 2, 3, 4]
print('Start iterating list a.')
print(type(a))
for i in a:
print(i)
print('After iteration, a is', a)
b = iter(a)
print('\nStart iterating iterator b.')
print(type(b))
for i in b:
print(i)
print('After iteration, b is', list(b))
for i in b:
print(i)
print('After iteration, b is', list(b))
# 运行结果:
# Start iterating list a.
#
# 1
# 2
# 3
# 4
# After iteration, a is [1, 2, 3, 4]
# Start iterating iterator b.
#
# 1
# 2
# 3
# 4
# After iteration, b is []
# After iteration, b is []
很明显,从运行结果可以看出,迭代器对象遍历元素是消耗型的,遍历结束之后,迭代器中的元素集为空,这类似于队列的出队操作,但实际上的原因是内存指针指向最后一个元素的内存地址后再继续向后移动指针就没有更多的元素,所以迭代器是一个一次性产品,如果想重复使用,就需要使用list()函数将迭代器转换为一个可迭代对象。
此外,迭代器对象和可迭代对象还有很重要的一个不同就是,可迭代对象在生成时将所有元素存入内存,然后迭代器对象只存储可迭代对象的第一个元素的地址,在遍历元素的时候,next()函数会移动内存指针,读取下一个元素。所以迭代器极大地节省了内存。更直观的例子如下:
import sys
a = [i for i in range(1, 10000000)]
print('The size of list a is', sys.getsizeof(a))
b = iter(a)
print('The size of iterator object b is', sys.getsizeof(b))
# 运行结果:
# The size of list a is 81528056
# The size of iterator object b is 56
生成器,顾名思义,就是数据的生成者,是一种特殊的迭代器,即它也实现了迭代协议,iter()和next()函数。由上一段落,可知,迭代器保存的是可迭代对象某个元素的地址,但是生成器保存的是生成新元素所使用的算法,换句话说生成器的元素都是动态生成的。在Python中,有两种创建生成器的方式,生成器表达式和生成器函数。
生成器表达式:
生成器表达式类似于可迭代对象的创建式,只是将[]换成()
a = [i for i in range(1, 10)]
print(type(a))
b = (i for i in range(1, 10))
print(type(b))
# 运行结果:
#
#
生成器函数:
判断生成器函数的标志是yield关键字,事实上,和普通函数使用return返回结果不同,生成器使用yield返回结果,同时挂起函数状态,下次再调用生成器的时候,从挂起处继续执行。举个烂大街的斐波那契栗子:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print('函数挂起, b =', b)
yield b
print('函数继续, b =', b)
a, b = b, a+b
n = n + 1
return "Generator done"
print(type(fib))
print(type(fib(5)))
for i in fib(5):
print('生成器生成:', i)
# 运行结果:
#
#
# 函数挂起, b = 1
# 生成器生成: 1
# 函数继续, b = 1
# 函数挂起, b = 1
# 生成器生成: 1
# 函数继续, b = 1
# 函数挂起, b = 2
# 生成器生成: 2
# 函数继续, b = 2
# 函数挂起, b = 3
# 生成器生成: 3
# 函数继续, b = 3
# 函数挂起, b = 5
# 生成器生成: 5
# 函数继续, b = 5
从上面的运行结果可以看出,生成器首先是一个函数,但是不同的是fib(5)并没有执行函数,而是返回一个生成器对象。此外,执行结果也显示出,yield会挂起函数执行状态,下次调用生成器时,会从挂起处继续向下执行。
在上面的栗子中,我们使用for…in…语句调用生成器,其原理是for…in…语句为我们创建了一个迭代器,然后使用next()方法进行迭代。
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print('函数挂起, b =', b)
yield b
print('函数继续, b =', b)
a, b = b, a+b
n = n + 1
return "Generator done"
fib_gen = fib(5)
while True:
try:
print('生成器生成:', next(fib_gen))
except StopIteration:
print('无更多元素可生成,生成器over.')
break
# 运行结果:
# 函数挂起, b = 1
# 生成器生成: 1
# 函数继续, b = 1
# 函数挂起, b = 1
# 生成器生成: 1
# 函数继续, b = 1
# 函数挂起, b = 2
# 生成器生成: 2
# 函数继续, b = 2
# 函数挂起, b = 3
# 生成器生成: 3
# 函数继续, b = 3
# 函数挂起, b = 5
# 生成器生成: 5
# 函数继续, b = 5
# 无更多元素可生成,生成器over.
另外一个重要的函数是send()函数,send()函数和next()函数类似,区别在于send()函数可以传递yield表达式的值,而next()不能传递特定的值,只能传递None进去。举个例子:
def subscriber():
feedback = 'Ack from subscriber'
while True:
print('生成器函数挂起')
n = yield feedback
print('此时的feedback为:', feedback)
print('Subscriber received:', n)
feedback = '200 OK'+ str(n)
def publisher(sub):
n = 0
print('Start generator.')
sub_rt = sub.send(None) # 或者next(sub)
print('Subscriber return:', sub_rt)
while n < 5:
n += 1
print('\nPublisher publish:', n)
sub_rt = sub.send(n)
print('Subscriber return:', sub_rt)
sub.close()
sub = subscriber()
publisher(sub)
# 运行结果:
# Start generator.
# 生成器函数挂起
# Subscriber return: Ack from subscriber
# Publisher publish: 1
# 此时的feedback为: Ack from subscriber
# Subscriber received: 1
# 生成器函数挂起
# Subscriber return: 200 OK1
# Publisher publish: 2
# 此时的feedback为: 200 OK1
# Subscriber received: 2
# 生成器函数挂起
# Subscriber return: 200 OK2
# Publisher publish: 3
# 此时的feedback为: 200 OK2
# Subscriber received: 3
# 生成器函数挂起
# Subscriber return: 200 OK3
# Publisher publish: 4
# 此时的feedback为: 200 OK3
# Subscriber received: 4
# 生成器函数挂起
# Subscriber return: 200 OK4
# Publisher publish: 5
# 此时的feedback为: 200 OK4
# Subscriber received: 5
# 生成器函数挂起
# Subscriber return: 200 OK5
从运行结果可以看出,启动生成器的作用就是将生成器运行至第一次遇到yield的那一行,yield返回feedback,换句话说就是只有当第一次触发yield语句的时候,生成器才算真正启动。在启动生成器之后第一次执行sub.send(n), 生成器函数(本例中的subscriber函数)首先在第5行取消生成器函数挂起状态并且将传入生成器的值赋给n,但是注意!!!此时yield语句并没有执行, 然后执行第6,7,4行,到回到第五行的时候再次遇到yield,生成器函数挂起,执行yield语句并返回feedback(在第七行做过更新)。
n = yield(feedback)这句话不太好理解,简单点说就是,调用者用send(n1)函数调用生成器后,第一次见到yield时执行n = yield feedback的左半部分,直接理解为n = n1,当第二次遇到yield的时候,才执行右半部分,yield feedback, 直接理解为return feedback
OK,了解了生成器,就可以进入正题了,协程。我们知道,在常规函数中,都是层级调用,层级返回,总是一个入口,一个返回。协程看上去也是一个函数,但是在执行的过程中可以中断,或称挂起,转而执行别的函数,到适当的时候再返回来继续向下执行。执行情况和多线程类似,但是协程只有一个线程。众所周知,多线程常通过加锁的方式控制各个线程对共享资源的抢占,然后切换线程,但是协程通常通过对共享资源状态的判断决定是否切换函数。简单一句话,多线程是抢占式的,协程是协作式的。
从上面对协程基本概念和特征的介绍,我们发现生成器函数其实就是实现了协程,即使用yield, send(), next()实现协程。
在Python3.4中,Python引入了异步IO的标准库asyncio来管理协程。asyncio中的基本概念:
先看一个简单的例子:
import asyncio
import time
import threading
def callback(future):
print('\nHappy ending:', future.result)
@asyncio.coroutine
def func1(x):
print('\nFunc1')
print('[Func1 Thread]', threading.currentThread())
if x > 5:
yield from func2(x - 5)
else:
yield from func3(5 - x)
print('Func1 done.')
@asyncio.coroutine
def func2(x):
print('\n[Func2] %d' % x)
print('[Func2 Thread]', threading.currentThread())
yield from background('Func2')
@asyncio.coroutine
def func3(x):
print('\n[Func3] %d' % x)
print('[Func3 Thread]', threading.currentThread())
yield from background('Func3')
@asyncio.coroutine
def background(x=None):
print('\n[Background Thread]', threading.currentThread())
if x == 'Func2':
print('[Background] Func2 done.')
elif x == 'Func3':
print('[Background] Func3 done.')
if __name__ == "__main__":
print("Coroutine Simple Example...\n")
loop = asyncio.get_event_loop()
cor1 = func1(10)
cor2 = func1(3)
print('func1() type:', type(cor1))
# ensure_future作用:如果参数为coroutine,创建task;如果为future,直接返回future
task1 = asyncio.ensure_future(cor1)
print('\nTask1 status', task1)
# add_done_callback作用:为task添加一个回调方法,在task执行结束之后调用该回调方法
task1.add_done_callback(callback)
print('Task1 status', task1)
# 创建一个task
task2 = loop.create_task(cor2)
print('\nTask2 status:', task2)
tasks = [task1, task2]
loop.run_until_complete(asyncio.wait(tasks))
print('\nResult:', result)
loop.close()
# 运行结果:
# Coroutine Simple Example...
# func1() type:
# Task1 status >
# Task1 status cb=[callback() at /Users/kangzhuangwei/Desktop/Test/test.py:6]>
# Task2 status: >
# Func1
# [Func1 Thread] <_MainThread(MainThread, started 140735963509696)>
# [Func2] 5
# [Func2 Thread] <_MainThread(MainThread, started 140735963509696)>
# [Background Thread] <_MainThread(MainThread, started 140735963509696)>
# [Background] Func2 done.
# Func1 done.
# Func1
# [Func1 Thread] <_MainThread(MainThread, started 140735963509696)>
# [Func3] 2
# [Func3 Thread] <_MainThread(MainThread, started 140735963509696)>
# [Background Thread] <_MainThread(MainThread, started 140735963509696)>
# [Background] Func3 done.
# Func1 done.
# Happy ending:
# ({ result=None>, result=None>}, set())
重点说一下这行:
loop.run_until_complete(asyncio.wait(tasks))
首先run_until_complete用来将协程包装成future对象(源码在下面), 这里的asyncio.wait主要做了两件事:
挂起wait函数状态直到所有的future都返回结果,或者触发timeout。查看源码可以看到wait函数的最后一行为
return (yield from _wait(fs, timeout, return_when, loop))
所以,其实传入run_until_complete的参数为_wait这个函数的一个返回值,继续看_wait,可以发现,这个函数创建了一个叫waiter的future对象,并且为来自wait函数的每一个future对象都添加了一个回调方法,然后_wait函数中出现了关键的一行代码:
yield from waiter
所以此时_wait函数挂起并且返回waiter对象,因此,传入run_until_complete函数的其实是waiter这个future对象。
下面是run_until_complete的源码:
def run_until_complete(self, future):
"""Run until the Future is done.
If the argument is a coroutine, it is wrapped in a Task.
WARNING: It would be disastrous to call run_until_complete()
with the same coroutine twice -- it would wrap it in two
different Tasks and that can't be good.
Return the Future's result, or raise its exception.
"""
self._check_closed()
new_task = not futures.isfuture(future)
future = tasks.ensure_future(future, loop=self)
if new_task:
# An exception is raised if the future didn't complete, so there
# is no need to log the "destroy pending task" message
future._log_destroy_pending = False
future.add_done_callback(_run_until_complete_cb)
try:
self.run_forever()
except:
if new_task and future.done() and not future.cancelled():
# The coroutine raised a BaseException. Consume the exception
# to not log a warning, the caller doesn't have access to the
# local task.
future.exception()
raise
finally:
future.remove_done_callback(_run_until_complete_cb)
if not future.done():
raise RuntimeError('Event loop stopped before Future completed.')
return future.result()