Python的协程真的有那么难吗?收藏!

从今天开始,咱们将开始进入Python的难点,那就是协程python

为了写明白协程的知识点,我查阅了网上的不少相关资料。发现很难有一个讲得系统,讲得全面的文章,致使咱们在学习的时候,每每半知半解,学完仍是一脸懵逼。编程

学习协程的第一门课程,是要认识生成器,有了生成器的基础,才能更好地理解协程bash

若是你是新手,那么你应该知道迭代器,对生成器应该是比较陌生的吧。不要紧,看完这系列文章,你也能从小白成功过渡为Python高手。多线程

再次提醒
本系列全部的代码均在Python3下编写,也建议你们尽快投入到Python3的怀抱中来。并发


. 可迭代、迭代器、生成器

初学Python的时候,对于这三货真的是傻傻分不清。甚至还认为他们是等价的。异步

其实,他们是不同的。异步编程

可迭代的对象,很好理解,咱们很熟悉的:字符串listdicttupledeque函数

为了验证我说的,须要借助collections.abc这个模块(Python2没有),使用isinstance()来类别一个对象是不是可迭代的(Iterable),是不是迭代器(Iterator),是不是生成器(Generator)。学习

import collections
from collections.abc import Iterable, Iterator, Generator

# 字符串
astr = "XiaoMing"
print("字符串:{}".format(astr))
print(isinstance(astr, Iterable))
print(isinstance(astr, Iterator))
print(isinstance(astr, Generator))

# 列表
alist = [21, 23, 32,19]
print("列表:{}".format(alist))
print(isinstance(alist, Iterable))
print(isinstance(alist, Iterator))
print(isinstance(alist, Generator))

# 字典
adict = {"name": "小明", "gender": "男", "age": 18}
print("字典:{}".format(adict))
print(isinstance(adict, Iterable))
print(isinstance(adict, Iterator))
print(isinstance(adict, Generator))

# deque
adeque=collections.deque('abcdefg')
print("deque:{}".format(adeque))
print(isinstance(adeque, Iterable))
print(isinstance(adeque, Iterator))
print(isinstance(adeque, Generator))
复制代码

输出结果spa

字符串:XiaoMing
True
False
False

列表:[21, 23, 32, 19]
True
False
False

字典:{'name': '小明', 'gender': '男', 'age': 18}
True
False
False

deque:deque(['a', 'b', 'c', 'd', 'e', 'f', 'g'])
True
False
False
复制代码

从结果来看,这些可迭代对象都不是迭代器,也不是生成器。它们有一个共同点,就是它们均可以使用for来循环。这一点,你们都知道,咱们就不去验证了。

扩展知识:
可迭代对象,是其内部实现了,__iter__ 这个魔术方法。
能够经过,dir()方法来查看是否有__iter__来判断一个变量是不是可迭代的。

接下来是,迭代器
对比可迭代对象,迭代器其实就只是多了一个函数而已。就是__next__(),咱们能够再也不使用for循环来间断获取元素值。而能够直接使用next()方法来实现。

迭代器,是在可迭代的基础上实现的。要建立一个迭代器,咱们首先,得有一个可迭代对象。
如今就来看看,如何建立一个可迭代对象,并以可迭代对象为基础建立一个迭代器。

from collections.abc import Iterable, Iterator, Generator

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


if __name__ == '__main__':
    my_list = MyList(5)  # 获得一个可迭代对象
    print(isinstance(my_list, Iterable))  # True
    print(isinstance(my_list, Iterator))  # False
    # 迭代
    for i in my_list:
        print(i)

    my_iterator = iter(my_list)  # 获得一个迭代器
    print(isinstance(my_iterator, Iterable))  # True
    print(isinstance(my_iterator, Iterator))  # True

    # 迭代
    print(next(my_iterator))
    print(next(my_iterator))
    print(next(my_iterator))
    print(next(my_iterator))
    print(next(my_iterator))
复制代码

输出

0
1
2
3
4

True
False

True
True

0
1
2
3
4
复制代码

若是上面的代码太多,也能够看这边,你更能理解。

from collections.abc import Iterator

aStr = 'abcd'  # 建立字符串,它是可迭代对象
aIterator = iter(aStr)  # 经过iter(),将可迭代对象转换为一个迭代器
print(isinstance(aIterator, Iterator))  # True
next(aIterator)  # a
next(aIterator)  # b
next(aIterator)  # c
next(aIterator)  # d
复制代码

扩展知识:
迭代器,是其内部实现了,__next__ 这个魔术方法。(Python3.x)
能够经过,dir()方法来查看是否有__next__来判断一个变量是不是迭代器的。

接下来,是咱们的重点,生成器

生成器的概念在 Python 2.2 中首次出现,之因此引入生成器,是为了实现一个在计算下一个值时不须要浪费空间的结构。

前面咱们说,迭代器,是在可迭代的基础上,加了一个next()方法。
而生成器,则是在迭代器的基础上(能够用for循环,可使用next()),再实现了yield

yield 是什么东西呢,它至关于咱们函数里的return。在每次next(),或者for遍历的时候,都会yield这里将新的值返回回去,并在这里阻塞,等待下一次的调用。正是因为这个机制,才使用生成器在Python编程中大放异彩。实现节省内存,实现异步编程。

如何建立一个生成器,主要有以下两种方法

  • 使用列表生成式
# 使用列表生成式,注意不是[],而是()
L = (x * x for x in range(10))
print(isinstance(L, Generator))  # True
复制代码
  • 实现yield的函数
# 实现了yield的函数
def mygen(n):
    now = 0
    while now < n:
        yield now
        now += 1

if __name__ == '__main__':
    gen = mygen(10)
    print(isinstance(gen, Generator))  # True
复制代码

可迭代对象和迭代器,是将全部的值都生成存放在内存中,而生成器则是须要元素才临时生成,节省时间,节省空间。


. 如何运行/激活生成器

因为生成器并非一次生成全部元素,而是一次一次的执行返回,那么如何刺激生成器执行(或者说激活)呢?

激活主要有两个方法

  • 使用next()
  • 使用generator.send(None)

分别看下例子,你就知道了。

def mygen(n):
    now = 0
    while now < n:
        yield now
        now += 1

if __name__ == '__main__':
    gen = mygen(4)

    # 经过交替执行,来讲明这两种方法是等价的。
    print(gen.send(None))
    print(next(gen))
    print(gen.send(None))
    print(next(gen))
复制代码

输出

0
1
2
3
复制代码


. 生成器的执行状态

生成器在其生命周期中,会有以下四个状态

GEN_CREATED # 等待开始执行
GEN_RUNNING # 解释器正在执行(只有在多线程应用中才能看到这个状态)
GEN_SUSPENDED # 在yield表达式处暂停
GEN_CLOSED # 执行结束

经过代码来感觉一下,为了避免增长代码理解难度,GEN_RUNNING这个状态,我就不举例了。有兴趣的同窗,能够去尝试一下多线程。如有疑问,可在后台回复我。

from inspect import getgeneratorstate

def mygen(n):
    now = 0
    while now < n:
        yield now
        now += 1

if __name__ == '__main__':
    gen = mygen(2)
    print(getgeneratorstate(gen))

    print(next(gen))
    print(getgeneratorstate(gen))

    print(next(gen))
    gen.close()  # 手动关闭/结束生成器
    print(getgeneratorstate(gen))
复制代码

输出

GEN_CREATED
0
GEN_SUSPENDED
1
GEN_CLOSED
复制代码


. 生成器的异常处理

在生成器工做过程当中,若生成器不知足生成元素的条件,就/应该 抛出异常(StopIteration)。

经过列表生成式构建的生成器,其内部已经自动帮咱们实现了抛出异常这一步。不信咱们来看一下。

因此咱们在本身定义一个生成器的时候,咱们也应该在不知足生成元素条件的时候,抛出异常。
拿上面的代码来修改一下。

def mygen(n):
    now = 0
    while now < n:
        yield now
        now += 1
    raise StopIteration

if __name__ == '__main__':
    gen = mygen(2)
    next(gen)
    next(gen)
    next(gen)
复制代码


. 从生成器过渡到协程:yield

经过上面的介绍,咱们知道生成器为咱们引入了暂停函数执行(yield)的功能。当有了暂停的功能以后,人们就想能不能在生成器暂停的时候向其发送一点东西(其实上面也有说起:send(None))。这种向暂停的生成器发送信息的功能经过 PEP 342 进入 Python 2.5 中,并催生了 Python协程的诞生。根据 wikipedia 中的定义

协程是为非抢占式多任务产生子程序的计算机程序组件,协程容许不一样入口点在不一样位置暂停或开始执行程序。

注意从本质上而言,协程并不属于语言中的概念,而是编程模型上的概念。

协程和线程,有类似点,多个协程之间和线程同样,只会交叉串行执行;也有不一样点,线程之间要频繁进行切换,加锁,解锁,从复杂度和效率来看,和协程相比,这确是一个痛点。协程经过使用 yield 暂停生成器,能够将程序的执行流程交给其余的子程序,从而实现不一样子程序的之间的交替执行。

下面经过一个简明的演示来看看,如何向生成器中发送消息。

def jumping_range(N):
    index = 0
    while index < N:
        # 经过send()发送的信息将赋值给jump
        jump = yield index
        if jump is None:
            jump = 1
        index += jump

if __name__ == '__main__':
    itr = jumping_range(5)
    print(next(itr))
    print(itr.send(2))
    print(next(itr))
    print(itr.send(-1))
复制代码

输出。

0
2
3
2
复制代码

这里解释下为何这么输出。
重点是jump = yield index这个语句。

分红两部分:

  • yield index 是将index return给外部调用程序。
  • jump = yield 能够接收外部程序经过send()发送的信息,并赋值给jump

以上这些,都是讲协程并发的基础必备知识请必定要亲自去实践并理解它,否则后面的内容,将会变得枯燥无味,晦涩难懂。

下一章,我将讲一个Python3.5新引入的语法:yield from。篇幅也比较多,因此就单独拿出来说。

             
相关文章
相关标签/搜索