大家好, 并发编程 进入第七篇.
从今天开始, 我们将开始进入 Python 的难点, 那就是协程.
为了写明白协程的知识点, 我查阅了网上的很多相关资料. 发现很难有一个讲得系统, 讲得全面的文章, 导致我们在学习的时候, 往往半知半解, 学完还是一脸懵逼.
学习协程的第一门课程, 是要认识生成器, 有了生成器的基础, 才能更好地理解协程.
如果你是新手, 那么你应该知道迭代器, 对生成器应该是比较陌生的吧. 没关系, 看完这系列文章, 你也能从小白成功过渡为 Ptyhon 高手.
再次提醒:
本系列所有的代码均在 Python3 下编写, 也建议大家尽快投入到 Python3 的怀抱中来.
本文目录
可迭代, 迭代器, 生成器
如何运行 / 激活生成器
生成器的执行状态
生成器的异常处理
从生成器过渡到协程: yield
. 可迭代, 迭代器, 生成器
初学 Python 的时候, 对于这三货真的是傻傻分不清. 甚至还认为他们是等价的.
其实, 他们是不一样的.
可迭代的对象, 很好理解, 我们很熟悉的: 字符串, list,dict,tuple,deque 等
为了验证我说的, 需要借助 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))
输出结果
字符串: 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
以上这些, 都是讲协程并发的基础必备知识, 请一定要亲自去实践并理解它, 不然后面的内容, 将会变得枯燥无味, 晦涩难懂.
来源: https://www.cnblogs.com/wongbingming/p/9060989.html