一 可迭代对象
可迭代对象:字符串、list、dict、tuple、deque
借助 from collections.abc import Iterable, Iterator, Generator 判断是否为可迭代的(Iterable)、是否为迭代器(Iterator)、是否为生成器(Generator)
import collections
from collections.abc import Iterable, Iterator, Generator
# 字符串
astr = 'XiaoMing'
print('字符串: {}'.format(astr))
print("astr是否可迭代: ", isinstance(astr, Iterable)) # True
print("astr是否是迭代器: ", isinstance(astr, Iterator)) # False
print("astr是否是生成器: ", isinstance(astr, Generator)) # False
# 列表
alist = [21, 23, 32,19]
print("列表:{}".format(alist))
print("alist是否可迭代: ", isinstance(alist, Iterable)) # True
print("alist是否是迭代器: ", isinstance(alist, Iterator)) # False
print("alist是否是生成器: ", isinstance(alist, Generator)) # False
# 字典
adict = {"name": "小明", "gender": "男", "age": 18}
print("字典:{}".format(adict))
print("adict是否可迭代: ", isinstance(adict, Iterable)) # True
print("adict是否是迭代器: ", isinstance(adict, Iterator)) # False
print("adict是否是生成器: ", isinstance(adict, Generator)) # False
# deque
adeque=collections.deque('abcdefg')
print("deque:{}".format(adeque))
print("adeque是否可迭代: ", isinstance(adeque, Iterable)) # True
print("adeque是否是迭代器: ", isinstance(adeque, Iterator)) # False
print("adeque是否是生成器: ", isinstance(adeque, Generator)) # False
可迭代对象是其内部实现了__iter__
方法
可通过 dir() 方法查看
二 迭代器
迭代器比可迭代对象多了一个函数而已,__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))
简化版
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__()
方法
三 生成器
生成器,则是在迭代器的基础上(可以用for 循环 可以使用next()),再实现了 yield
yield
是什么?
答:它相当于函数里的return,再每次next(),或者佛如遍历的时候,都会yield
这里将新的值返回去,并在这里阻塞,等待下次的调用,正是由于这个机制,才使用生成器再Python中大放异彩,节省内存,实现异步编程
创建生成器的方法
方法1:
# 使用列表生成 注意是() 不是[]
L = (x*x for x in range(10))
print(isinstance(L, Generator)) # True
方法2:
# 实现了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
可迭代对象和迭代器是将所有的值都生成存在内存中,而生成器则是需要元素才临时生成,节省时间,节省空间
四 运行和激活生成器
激活方法:
1 使用next()
2 使用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))
生成器的执行状态
生成器在其生命周期中,会有如下四种状态
GEN_CREATED # 等待开始执行
GEN_RUNING # 解释器正在执行(只有再多线程应用中才能看到这个状态)
GEN_SUSPENDED # 再yield 表达式处暂停
GEN_CLOSED # 执行结束
from inspect import getgeneratorstate
def mygen(n):
now = 0
while now < n:
yield now
now += 1
if __name__ == '__main__':
gen = mygen(4)
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 暂停生成器,可以将程序的执行流程交给其他的子程序,从而实现不同子程序的之间的交替执行。
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))
jump = yield index
分成两部分看:
1 yield index 式将index return 给外部调用程序
2 jump = yield 可以接收外部程序通过send()发送的信息,并赋值给jump
思考问题:
假如没有协程,需要写一个并发程序,可能存在的问题
1 使用最常规的同步编程要实现异步并发效果并不理想忙活着难度极高
2 由于GIL锁的存在,多线程的运行需要频繁的枷锁解锁,切换线程,极大的降低了并发性能
而协程的出现解决了这个问题,特点:
1 协程的单线程里实现任务的切换的
2 利用同步的方式实现异步
3 不再需要锁,提高了并发性能
五 yield from 的用法
yield from py3.3 语法
yield from 后面需要加的可迭代对象,它可以式普通的可迭代对象,也可以式迭代器,甚至是生成器
# 字符串
astr='ABC'
# 列表
alist=[1,2,3]
# 字典
adict={"name":"wangbm","age":18}
# 生成器
agen=(i for i in range(4,8))
def gen(*args, **kw):
for item in args:
for i in item:
yield i
new_list=gen(astr, alist, adict, agen)
print(list(new_list))
# ['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]
# 字符串
astr='ABC'
# 列表
alist=[1,2,3]
# 字典
adict={"name":"wangbm","age":18}
# 生成器
agen=(i for i in range(4,8))
def gen(*args, **kw):
for item in args:
yield from item
new_list=gen(astr, alist, adict, agen)
print(list(new_list))
# ['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]
对比yield 来说代码更加简洁,结构更加清晰
复杂应用: 生成器的嵌套
讲它之前,首先知道几个概念
1 调用方:调用委派生成器的客户端(调用方)代码
2 委托生成器:包含 yield from 表达式的生成器函数
3 子生成器:yield from 后面加的生成器函数
# 比如,第一次传入10,那返回平均数自然是10.
# 第二次传入20,那返回平均数是(10+20)/2=15
# 第三次传入30,那返回平均数(10+20+30)/3=20
# 子生成器
def average_gen():
total = 0
count = 0
average = 0
while True:
new_num = yield average
count += 1
total += new_num
average = total/count
# 委托生成器
def proxy_gen():
while True:
yield from average_gen()
# 调用方
def main():
calc_average = proxy_gen()
next(calc_average) # 预激下生成器
print(calc_average.send(10)) # 打印:10.0
print(calc_average.send(20)) # 打印:15.0
print(calc_average.send(30)) # 打印:20.0
if __name__ == '__main__':
main()
委托生成器的作用是:在调用方与子生成器之间建立一个双向通道
双向通道是什么意思?
调用方可以通过send()直接发送消息给子生成器,而子生成器yield 的值,也是直接返回给调用方。
你可能会经常看到有些代码,还可以在yield from前面看到可以赋值。这是什么用法?
你可能会以为,子生成器yield回来的值,被委托生成器给拦截了。你可以亲自写个demo运行试验一下,并不是你想的那样。
因为我们之前说了,委托生成器,只起一个桥梁作用,它建立的是一个双向通道,它并没有权利也没有办法,对子生成器yield回来的内容做拦截。
为了解释这个用法,我还是用上述的例子,并对其进行了一些改造。添加了一些注释,希望你能看得明白。
按照惯例,我们还是举个例子。
# 子生成器
def average_gen():
total = 0
count = 0
average = 0
while True:
new_num = yield average
if new_num is None:
break
count += 1
total += new_num
average = total/count
# 每一次return,都意味着当前协程结束。
return total,count,average
# 委托生成器
def proxy_gen():
while True:
# 只有子生成器要结束(return)了,yield from左边的变量才会被赋值,后面的代码才会执行。
total, count, average = yield from average_gen()
print("计算完毕!!\n总共传入 {} 个数值, 总和:{},平均数:{}".format(count, total, average))
# 调用方
def main():
calc_average = proxy_gen()
next(calc_average) # 预激协程
print(calc_average.send(10)) # 打印:10.0
print(calc_average.send(20)) # 打印:15.0
print(calc_average.send(30)) # 打印:20.0
calc_average.send(None) # 结束协程
# 如果此处再调用calc_average.send(10),由于上一协程已经结束,将重开一协程
if __name__ == '__main__':
main()
yield from 的作用:处理异常