迭代器,生成器与协程

迭代器,生成器与协程 —— 抛砖引玉

在学习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语言,但也有其他语言支持。协程更适合于用来实现彼此熟悉的程序组件,如协作式多任务、异常处理、事件循环、迭代器、无限列表和管道。

进程,线程,协程

对操作系统来说,线程是最小的执行单元,进程是最小的资源管理单元。

无论进程还是线程,都是由操作系统所管理的。

多线程的问题:

  1. 涉及到同步锁。

  2. 涉及到线程阻塞状态和可运行状态之间的切换。

  3. 涉及到线程上下文的切换。

以上涉及到的任何一点,都是非常耗费性能的操作。

协程的优势:

  • 执行效率极高,因为子程序切换(函数)不是线程切换,由程序自身控制,没有切换线程的开销。所以与多线程相比,线程的数量越多,协程性能的优势越明显。
  • 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在控制共享资源时也不需要加锁,因此执行效率高很多。

协程 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开始引入了新的语法asyncawait,可以让coroutine的代码更简洁易读。

  1. @asyncio.coroutine替换为async
  2. 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'),
])
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值