迭代器,生成器与协程 —— 抛砖引玉
在学习Python协程之前首先需要了解Python生成器的概念,而生成器又是一种特殊的迭代器,所以一切从迭代器开始
迭代器(Iterators)
迭代器是Python语言的一大特性
可迭代
什么是可迭代(可迭代对象)?
- 遵循了可迭代协议的对象
- 简单可理解为:凡是可以for循环的都属于iterable可迭代对象;列表,字典,字符串等等都是可迭代对象
例子1.1:可迭代对象示例(列表,字符串)
# 例子1:可迭代对象示例(列表,字符串)
L1 = [1,2,3]
for i in L1:
print(i)
S = "Hello World!"
print(S)
for j in S:
print(j)
可迭代协议:含iter()方法。且可迭代对象中的iter()方法返回的是一个对应的迭代器。(如list对应的迭代器就是list_iterator)。
例子1.2 :iter()方法返回迭代器
# 例子1.2 :iter()方法返回迭代器
L1 = [1,2,3]
type(L1.__iter__())
for i in L1.__iter__():
print(i)
简单的说:在Python
中任意的对象,只要它定义了可以返回一个迭代器的__iter__
方法,或者支持下标索引的__getitem__
方法,那么它就是一个可迭代对象。
迭代器
什么是迭代器?
提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。
- 遵循了迭代器协议的对象
- 简单可理解为:凡是可以正常next()的都是iterator 迭代器
迭代器协议:
- 含iter()方法。且方法返回的Iterator对象本身
- 含next()方法,每当next()方法被调用,返回下一个值,直到没有值可以访问,这个时候会抛出StopIteration的异常。
简单的说:实现了 __iter__
和 __next__
方法的对象就是迭代器, 其中, __iter__
方法返回迭代器对象本身, next
方法返回容器的下一个元素, 在没有后续元素时抛出 StopIteration 异常。
例子1.3:自定义一个迭代器
# 例子1.3:自定义一个迭代器 斐波那契数列 1 1 2 3 5
class Fib:
def __init__(self, max):
self.a = 0
self.b = 1
self.max = max
def __iter__(self):
return self
def __next__(self):
fib = self.a
if fib > self.max:
raise StopIteration
self.a, self.b = self.b, self.a + self.b
return fib
f = Fib(100)
for i in f:
print(i)
print("next ", next(f))
可迭代对象与迭代器对象
迭代器与可迭代对象不同的是,迭代器对象不仅需要实现iter()方法,它还需要实现next()方法,即迭代器协议,事实上任何实现了iter()和next()方法的类对象都可以是迭代器对象。
- 迭代器肯定是可迭代对象;但是可迭代对象却不一定是迭代器。
- 非迭代器的可迭代对象iter方法返回迭代器;迭代器的iter返回自身
- 非迭代器的可迭代对象只有iter()方法;迭代器有iter(),next()两个方法
class MyList(object): # 定义可迭代对象类
def __init__(self, num):
self.end = num # 上边界
# 返回一个实现了__iter__和__next__的迭代器类的实例
def __iter__(self):
return MyListIterator(self.end)
class MyListIterator(object): # 定义迭代器类
def __init__(self, end):
self.data = end # 上边界
self.start = 0
# 返回该对象的迭代器类的实例;因为自己就是迭代器,所以返回self
def __iter__(self):
return self
# 迭代器类必须实现的方法,若是Python2则是next()函数
def __next__(self):
while self.start < self.data:
self.start += 1
return self.start - 1
raise StopIteration
例子1:迭代器是消耗型
# 例子1:迭代器是消耗型
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)
b = a.__iter__()
print('\nStart iterating iterator b.')
print(type(b))
for i in b:
print(i)
print('After iteration, b is', list(b))
例子2:迭代器未保存所有元素
# 例子2:迭代器未保存所有元素
import sys
a = [0,1,2,3,4,5,6,7,8,9]
for i in a:
print(i)
print('The size of list a is', sys.getsizeof(a))
# b = iter(a)
b = a.__iter__()
for j in b:
print(j)
print('The size of iterator object b is', sys.getsizeof(b))
可迭代对象转化为迭代器对象:
iter() # python 内置函数
__iter__() # 对象内定义的
生成器(Generator)
生成器
什么是生成器?
如果列表元素可以按照某种算法推算出来,那我们就可以在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,从而节省大量的空间,在Python中,这种一边循环一边计算的机制,称为生成器:generator
生成器(Generator)是一种使用普通函数语法定义的迭代器。生成器和普通函数的区别是使用yield,而不是return返回值。
例子2.1:生成器示例
def gen():
yield from gen2
yield 2
def gen2()
g = gen()
type(g)
next(g)
g.__next__()
for i in gen():
print(i)
简单地讲,yield
的作用就是把一个函数变成一个generator
,带有yield
的函数不再是一个普通函数,Python
解释器会将其视为一个 generator
,返回一个 iterable
对象。
在for
循环执行时,每次循环都会执行函数内部的代码,执行到yield x
时,函数就返回一个迭代值,下次迭代时,代码从yield x
的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到yield
。
例子2.2 :生成器实现斐波那契
def fab(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
f = fab(10)
for i in f:
print(i)
创建生成器的两种方法
- yield
- 生成器表达式
# 列表表达式
L = [i for i in range(10)]
for j in L:
print(j)
type(L)
# 生成器表达式
G = (i for i in range(10))
for j in G:
print(j)
type(G)
# 集合表达式
{}
激活生成器
- 使用
next()
- 使用
generator.send(None)
生成器的执行状态
生成器在其生命周期中,会有如下四个状态
GEN_CREATED
# 等待开始执行GEN_RUNNING
# 解释器正在执行(只有在多线程应用中才能看到这个状态)GEN_SUSPENDED
# 在yield表达式处暂停GEN_CLOSED
# 执行结束
例子:
from inspect import getgeneratorstate
def mygen(n):
now = 0
while now < n:
yield now
now += 1
gen = mygen(2)
print(getgeneratorstate(gen))
print(next(gen))
print(getgeneratorstate(gen))
print(next(gen))
gen.close() # 手动关闭/结束生成器
print(getgeneratorstate(gen))
协程(Coroutine)
协程,又称微线程,纤程,英文名Coroutine。协程的作用,是在执行函数A时,可以随时中断,去执行函数B,然后中断继续执行函数A(可以自由切换)。这一整个过程看似像多线程,然而协程只有一个线程执行。
最早的定义:协程的概念最早由Melvin Conway在1963年提出并实现,用于简化COBOL编译器的词法和句法分析器间的协作,当时他对协程的描述是“行为与主程序相似的子例程”。
Wiki的定义:协程是计算机程序的一类组件,推广了协作式多任务的子程序,允许执行被挂起与被恢复。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。协程源自Simula和Modula-2语言,但也有其他语言支持。协程更适合于用来实现彼此熟悉的程序组件,如协作式多任务、异常处理、事件循环、迭代器、无限列表和管道。
进程,线程,协程
对操作系统来说,线程是最小的执行单元,进程是最小的资源管理单元。
无论进程还是线程,都是由操作系统所管理的。
多线程的问题:
- 涉及到同步锁。
- 涉及到线程阻塞状态和可运行状态之间的切换。
- 涉及到线程上下文的切换。
以上涉及到的任何一点,都是非常耗费性能的操作。
协程的优势:
- 执行效率极高,因为子程序切换(函数)不是线程切换,由程序自身控制,没有切换线程的开销。所以与多线程相比,线程的数量越多,协程性能的优势越明显。
- 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在控制共享资源时也不需要加锁,因此执行效率高很多。
协程 in Pyhon
通过上面的介绍,我们知道生成器为我们引入了暂停函数执行(yield
)的功能。当有了暂停的功能之后,人们就想能不能在生成器暂停的时候向其发送一点东西(其实上面也有提及:send(None)
)。这种向暂停的生成器发送信息的功能通过 PEP 342
进入 Python 2.5
中,并催生了 Python
中协程
的诞生。根据 wikipedia
中的定义:
协程是为非抢占式多任务产生子程序的计算机程序组件,协程允许不同入口点在不同位置暂停或开始执行程序。
yield(纯手动)
def producer(obj):
obj.send(None)
for i in range(5):
print("我生产了:{}".format(i))
r = obj.send(i)
print("消费者返回说:{}".format(r))
def consumer():
while True:
receive = yield "200 我已收到"
print("我收到了:{}".format(receive))
cons = consumer()
producer(cons)
整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。
asyncio(半自动)
asyncio
是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。
- event_loop 事件循环:程序开启一个无限循环,把一些函数注册到事件循环上,当满足事件发生的时候,调用相应的协程函数
- coroutine 协程:协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。
- task 任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含了任务的各种状态
- future: 代表将来执行或没有执行的任务的结果。它和task上没有本质上的区别
- async/await 关键字:python3.5用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。
@asyncio.coroutine
把一个generator标记为coroutine类型,然后,我们就把这个coroutine
扔到EventLoop
中执行。
例子1:假设sleep是一个耗时的IO操作
import threading
import asyncio
@asyncio.coroutine
def hello():
print('Hello world! (%s)' % threading.currentThread())
yield from asyncio.sleep(1)
print('Hello again! (%s)' % threading.currentThread())
# 创建一个事件循环
loop = asyncio.get_event_loop()
tasks = [hello1(), hello2()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
例子2:打开网页试试
import asyncio
@asyncio.coroutine
def wget(host):
print('wget %s...' % host)
connect = asyncio.open_connection(host, 80)
reader, writer = yield from connect
header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host
writer.write(header.encode('utf-8'))
yield from writer.drain()
while True:
line = yield from reader.readline()
if line == b'\r\n':
break
print('%s header > %s' % (host, line.decode('utf-8').rstrip()))
# Ignore the body, close the socket
writer.close()
loop = asyncio.get_event_loop()
tasks = [wget(host) for host in ['w3.huawei.com', 'xinsheng.huawei.com', 'mbbcn.huawei.com']]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
async/await
Python 3.5开始引入了新的语法async
和await
,可以让coroutine的代码更简洁易读。
- 把
@asyncio.coroutine
替换为async
- 把
yield from
替换为await
。
例2.1:
import asyncio
async def wget(host):
print('wget %s...' % host)
connect = asyncio.open_connection(host, 80)
reader, writer = await connect
header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host
writer.write(header.encode('utf-8'))
await writer.drain()
while True:
line = await reader.readline()
if line == b'\r\n':
break
print('%s header > %s' % (host, line.decode('utf-8').rstrip()))
# Ignore the body, close the socket
writer.close()
loop = asyncio.get_event_loop()
tasks = [wget(host) for host in ['w3.huawei.com', 'xinsheng.huawei.com', 'mbbcn.huawei.com']]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
gevent(全自动)
Python通过yield
提供了对协程的基本支持,但是不完全。而第三方的gevent为Python提供了比较完善的协程支持。
例1:
from gevent import monkey; monkey.patch_socket()
import gevent
def f(n):
for i in range(n):
print(gevent.getcurrent(), i)
gevent.sleep(0)
g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()
例2:
from gevent import monkey; monkey.patch_socket()
import gevent
import urllib.request
def f(url):
print('GET: %s' % url)
resp = urllib.request.urlopen(url)
data = resp.read()
print('%d bytes received from %s.' % (len(data), url))
# print("begin")
# f("http://w3.huawei.com")
gevent.joinall([
gevent.spawn(f, 'http://w3.huawei.com'),
gevent.spawn(f, 'http://xinsheng.huawei.com'),
gevent.spawn(f, 'http://mbbcn.huawei.com'),
])