协程,比较难。花费好长时间还没太懂。从头梳理一下。
可迭代
可迭代对象(iterable):Python中任意的对象,只要它定义了可以返回一个迭代器的__iter__
方法,
或者定义了可以支持下标索引的__getitem__
方法(两个魔法方法),那么它就是一个可迭代对象。
简单说,可迭代对象就是能提供迭代器的任意对象。比如说,字符串,列表(list),字典(dict),元组(tuple)等。
from collections.abc import Iterable, Iterator, Generator
str_1 = 'sixkery'
print('字符串是否是可迭代对象:',isinstance(str_1,Iterable))
print('字符串是否是迭代器:',isinstance(str_1,Iterator))
print('字符串是否是生成器:',isinstance(str_1,Generator))
字符串是否是可迭代对象: True
字符串是否是迭代器: False
字符串是否是生成器: False
list_1 = [1,2,3,4]
print('列表是否是可迭代对象:',isinstance(list_1,Iterable))
print('列表是否是迭代器:',isinstance(list_1,Iterator))
print('列表是否是生成器:',isinstance(list_1,Generator))
列表是否是可迭代对象: True
列表是否是迭代器: False
列表是否是生成器: False
dict_1 = {'name':'小沐','age':23}
print('字典是否是可迭代对象:',isinstance(dict_1,Iterable))
print('字典是否是迭代器:',isinstance(dict_1,Iterator))
print('字典是否是生成器:',isinstance(dict_1,Generator))
字典是否是可迭代对象: True
字典是否是迭代器: False
字典是否是生成器: False
可以看到这些都是可迭代对象,可以用方法 dir() 查看是否有 __iter__
来判断一个变量是否是可迭代对象。可迭代对象都可以使用 for 循环。
if '__iter__' in dir(list()):
print('这回信了吧')
这回信了吧
迭代器
迭代器,是在可迭代对象的基础上实现的,创建一个迭代器,首先要用一个可迭代对象。
str_1 = 'asdfg' # 字符串,是可迭代对象
alterator = iter(str_1) # 通过方法 iter() 把字符串变成迭代器
print('是否成功转换成迭代器:',isinstance(alterator,Iterator))
是否成功转换成迭代器: True
next(alterator)
'a'
next(alterator)
's'
next(alterator)
'd'
for i in alterator:
print(i)
f
g
结果可以看出,迭代器比可迭代的对象多了一个函数 next() 。我们可以用它来获取元素。同样 for 循环也是可以的。
在迭代器的内部实现了 __next__
方法。
重点:生成器
之所以引入生成器,是因为实现一个在计算下一个值时不需要浪费空间的结构。
之前说的迭代器,是在可迭代对象的基础上加了一个 next() 方法。
而生成器,是在迭代器的基础上,在实现了 yield 。所以生成器可以使用 for 循环,可以使用 next()。
yield 是啥呢?它相当于我们函数中的 return 。在每次 next() ,或者 for 循环遍历的时候,都会在 yield 的地方将新的值返回回去。并在这里阻塞,等待下一次的调用。正是有了这个机制,才使得生成器在 Python 编程中大放异彩。实现节省内存,实现异步编程。
如何创建一个生成器,主要有以下两种方式:
1、使用列表生成式
# 使用列表生成式,注意不是 [] ,而是 ()
a = (x*x for x in range(1,5))
for i in a:
print(i)
1
4
9
16
a = (x*x for x in range(1,5))
next(a)
1
next(a)
4
a = (x*x for x in range(1,5))
print('是否是生成器:',isinstance(a,Generator))
是否是生成器: True
2、实现 yield 的函数
def my_gen(n):
a = 0
while a < n:
yield a
a += 1
if __name__ == '__main__':
gen = my_gen(5)
print('是否是生成器:',isinstance(gen,Generator))
for i in gen:
print(i)
是否是生成器: True
0
1
2
3
4
总结:可迭代对象和迭代器,是将所有的值计算出来存放在内存中,而生成器是不不计算,等你要的时候在计算,这时候就节约了内存。
如何运行/激活生成器
由于生成器并不是一次生成所有的元素,而是一次一次的执行,那么如何刺激生成器执行呢?
激活主要有两种方式
- 使用 next()
- 使用 genertor.send(None)
def my_gen(n):
a = 0
while a < n:
yield a
a += 1
if __name__ == '__main__':
gen = my_gen(5)
print(next(gen))
print(next(gen))
print(gen.send(None))
print(gen.send(None))
0
1
2
3
我觉得还是使用 for 循环来得快。
生成器的执行状态
生成器在其生命周期中,会用一下四个状态
- GEN_CREATED :等待开始执行
- GEN_RUNNING :解释器正在执行(只有再多线程应用中才能看到这个状态)
- GEN_SUSPENDED :在 yield 表达式出暂停
- GEN_CLOSED :执行结束
from inspect import getgeneratorstate
def my_gen(n):
a = 0
while a < n:
yield a
a += 1
if __name__ == '__main__':
gen = my_gen(5)
print(getgeneratorstate(gen)) # 等待开始执行
print(next(gen))
print(getgeneratorstate(gen)) # 在 yield 表达式出暂停
print(next(gen))
gen.close() # 手动关闭结束生成器
print(getgeneratorstate(gen)) # 执行结束
GEN_CREATED
0
GEN_SUSPENDED
1
GEN_CLOSED
生成器的异常处理
在生成器工作中,若生成器不满足生成元素的条件,获取没有元素生成了,就会抛出异常(StopIteration)。
a = (x*x for x in range(1,3))
next(a)
next(a)
next(a)
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-50-0f82892640d1> in <module>()
2 next(a)
3 next(a)
----> 4 next(a)
StopIteration:
所以在定义生成器时,要考虑这个问题,在不满足生成元素条件的时候,抛出异常:
def my_gen(n):
a = 0
while a < n:
yield a
a += 1
raise StopIteration
if __name__ == '__main__':
gen = my_gen(2)
next(gen)
next(gen)
next(gen)
从生成器过度到协程:yield
上面的介绍,知道生成器为我们引入了暂停函数执行(yield)的功能。当我们有了暂停函数的功能之后,就想能不能在生成器暂停的时候向生成器发送一点东西(gen.send(None))这种机制催生的协程的诞生。
协程:协程是为非抢占式多任务产生子程序组件协程允许不同入口点在不同位置暂停或开始执行任务。
从本质上看,协程并不属于某种语言的概念,而是编程模型上的概念。
协程和线程一样都能交叉串行执行任务,但是协程频繁加锁解锁,协程切换。协程只要在yield暂停处把任务交到别处执行。协程还是很有发展潜力的。
def fn(n):
a = 0
while a < n:
jump = yield a
if jump is None:
jump = 1
a += jump
if __name__ == '__main__':
itr = fn(5)
print(next(itr))
print(itr.send(2))
0
2
yield a 是将 a 返回出去
jump = yield 是接收传递进来的值。