迭代器
可迭代的或迭代对象
可迭代的:内部含有__iter__方法的数据类型叫可迭代的,也叫迭代对象 , range是一个迭代对象,内部含有iter()方法。为什么可迭代对象能被for 循环,因为可迭代对象含有iter方法,只要函数iter方法的对象就可以被for循环。这也是可迭代协议。
运用dir()方法来测试一个数据类型是不是可迭代的的。如果含有iter的,就是可迭代对象、
迭代器协议
迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 (只能往后走不能往前退)。
迭代器:可以被next()
函数调用并不断返回下一个值的对象称为迭代器,迭代器是一个实现了迭代器协议的对象。也可以这样说包含next方法的可迭代对象叫迭代器
迭代器和列表的区别?
区别在于节省内存和惰性运算
如果我有一个列表中有500个数据,那么这个列表就非常占用内存,如果把列表变为迭代器,就占用很少的内存,为什么会占用内存少呢,因为迭代器只记录当前这元素和下一个元素,当你找我要的时候我才在内存中生成数据,不找我要,就不生成数据,也就不占用内存,这就是迭代器的惰性运算。
如何区分迭代器和可迭代对象?这个在我们时间长了后会混淆。
可迭代对象最简单的定义:可以使用for in 语句进行循环的对象。比如字符串、列表、元组、字典以及迭代器、生成器都是可迭代对象。而迭代器是可以使用next()进行回调的对象,迭代器比可迭代对象多一个__next__方法。可迭代对象和迭代器的联系是:可以对迭代对象使用iter()方法来生成迭代器。
判断一个变量是不是迭代器或者可迭代对象?
from collections import Iterator
from collections import Iterable
print(isinstance([1,2,3,4],Iterable))
str_iter="abc".__iter__()
print(isinstance(str_iter,Iterator))
结果:
True
True
注意!!!文件和enumerate含有next和iter方法所以是迭代器,例如:
enumerate范例:
from collections import Iterator
l=[1,3,4]
print(isinstance(enumerate(l),Iterator))
结果:
True
文件范例:
with open("产品",encoding="utf-8") as f: print("__next__" in dir(f))
结果:
True
迭代器的特点:
1.节省内存
2.惰性运算(什么时候用到,什么时候运行)
3.从前到后一次取值,过程不可逆,不可重复。
如果把迭代对象转变为迭代器?
可迭代对象调用自己的__iter__().方法就会返回一个迭代器
迭代器=iter(迭代对象) 注意:iter(迭代对象)=迭代对象.__iter__() next 和它的用法一样
迭代器的方法:
1.__next__方法:返回迭代器的下一个 元素。
2.__iter__方法:返回迭代器对象本身。
l=["ha","hei","he"] ret=l.__iter__() #这个步骤生成迭代器,ret就称为了一个迭代器。 print(ret.__next__()) print(ret.__next__())
迭代器比可迭代对象多一个__next__方法
包含__next__方法的可迭代对象就是迭代器
for循环原理
1.先判断对象是不是可迭代对象,如果不是直接报错,如果是的话,调用__iter__()返回一个迭代器。
2.然后不断的调用生成的迭代器的next()方法,每次返回一个值。
3.迭代到最后,没有更多元素了,就抛出异常 StopIteration,这个异常 python 自己会处理,不会暴露给开发者
这也就是用for循环取值节省内存的原因。
模拟for循环,解释for循环的内部原理。
不加try ....except 的情况下:
范例一:
l=[1,2,3,4,5] ite=l.__iter__() while True:
print(ite.__next__())
结果:
1 2 3 4 5 StopIteration 出现这种原因是while 循环不可以自动停止,然而迭代器从前到后一次取值,
过程不可逆,不可重复,所以就出现了这种情况。
如何避免这种错误?
范例二:
l=[1,2,3,4,5] ite=l.__iter__() while True: try: print(ite.__next__()) except StopIteration: break
结果:
1 2 3 4 5
总结:for循环是让我们更简单的使用迭代器,用迭代器取值不需要关心索引或者key.
作者:酱油哥
链接:https://www.zhihu.com/question/20829330/answer/286837159
生成器
为什么要有生成器?
迭代器是从集合中取数据,而生成器是创造数据,这点从斐波那契中就可以看出区别来了,我们知道斐波那契数是无穷的,在集合中放不下,那么我们如何生成所有的斐波那契呢,这就用到了生成器
例如
def f(): a=0 b=1 while True: yield a yield b a = b + a b=b+a for i in f(): print(i)
含有有yield 的函数被称之为生成器(generator)函数。
生成器函数执行后会得到一个生成器(generator)
生成器本质:生成器是迭代器,它包含一切迭代器的方法。
生成器的作用是
- 延迟计算一次只产生一个数据项.
- 增加代码的可阅读性
yield关键字
yield的作用:
- 记住上次执行的状态
- 自动切换到不同任务
- 1次只返回一个结果, 把返回值传递给next() 的调用方
调用生成器send方法传递数据时,必须先调用next(g)或者g.send(None)方法,执行到yield语句,等待接收数据。否则会报错。
def func(): a = yield 5 # send中的值先找到上次暂停的位置,然后把yield 5 替换成world 这里就变成了 a="world print("a>>>", a) yield 22 g = func() num = g.__next__() print("num>>>",num) foo = g.send('world') # send相当于next(),但是他又和next又有不同,他可以传值给上次暂停的地方,但是send第一次不能先执行,必须先执行next()或者send(none) print('foo>>', foo)
结果:
num>>> 5 a>>> world foo>> 22
实现yield 的切换任务的例子,即实现协程的例子
import time def consumer(): r = "开始吃包子了~" while True: x = yield r #r发给send的调用方,x 接收send的传的值 print("我正在吃包子%s"%(x)) r = "包子已经收到" time.sleep(1) def producer(c): p = c.__next__() print(p) n = 0 while n < 5: n = n + 1 print("生产者生产包子%s" % n) nn = c.send(n) print("send****") print("收到消费者的消息为%s"%nn) c.close() con = consumer() producer(con)
结果:
开始吃包子了~ 生产者生产包子1 我正在吃包子1 send**** 收到消费者的消息为包子已经收到 生产者生产包子2 我正在吃包子2 send**** 收到消费者的消息为包子已经收到 生产者生产包子3 我正在吃包子3 send**** 收到消费者的消息为包子已经收到 生产者生产包子4 我正在吃包子4 send**** 收到消费者的消息为包子已经收到 生产者生产包子5 我正在吃包子5 send**** 收到消费者的消息为包子已经收到
yield能在两个任务之间保存状态和切换,实现了并发的效果.,但是这种并发没有什么意义,只有遇到io阻塞时切换并发才有意义
yield from 关键字
yield from 是在python3.3中出现的新语法.
如果一个生成器需要另一个生成器的值时,传统的方法就是用for循环,yield from就可以带起for循环
yield from 的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起
来,这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中
添加大量处理异常的样板代码
传统方式 for循环
def generator1(): item = range(5) for i in item: yield i def generator2(): yield 'a' yield 'b' yield 'c' for i in generator1(): yield i yield 'd' for i in generator2() : print(i)
使用yield from 语法
def generator1(): item = range(5) for i in item: yield i def generator2(): yield 'a' yield 'b' yield 'c' yield from generator1() #yield from iterable本质上等于 for item in iterable: yield item的缩写版 yield 'd' for i in generator2() : print(i)
结果:
a b c 0 1 2 3 4 d
一个题目:
a="AB"
b="CD"
想要生成: A B C D
方法一:
def func(): a="AB" b="CD" for i in a: yield i for i in b : yield i f=func() for i in f: print(i)
方法二
def func(): a="AB" b="CD" yield from a yield from b f=func() for i in f: print(i)
yield from 调用生成器
def htest(): i = 1 while i < 4: n = yield i print("nnn",n) if i == 3: return 100 i += 1 def itest(): val = yield from htest() #调用生成器接收,yield from可以接收return的返回值 print("val>>>",val) t = itest() t.send(None) j = 0 while j < 3: j += 1 try: print('j',j) t.send(j) #当t.send(3)时,htest函数早已经return了结束了 except StopIteration as e: print('异常了')
j 1 nnn 1 j 2 nnn 2 j 3 nnn 3 val>>> 100 异常了
创建生成器的两种方法
1.生成器函数。常规函数定义,但是使用yield语句而不是return语句返回结果,yield返回值并不会终止程序的运行,一次只返回一个结果,在每个结果中间它会记住函数状态,以便下次从它离开的地方开始。
2.生成器表达式:
要介绍生成器表达式前,要先介绍列表生成式,即列表推导式,说白了就是如何生成一个列表
常规的写法 egg_list=[] for i in range(10): egg_list.append('鸡蛋%s' %i) 如果用列表推导式可以这么写 语法规则: for 左边是列表 中要存放的结果,for 右边是生成这个列表所需的条件. ret=['鸡蛋%s'%i for i in range(10)] print (ret)
优点:方便,改变了编程习惯,可称之为声明式编程
结果:
['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']
#1、把列表推导式的[]换成()就是生成器表达式 #2、示例:生一筐鸡蛋变成给你一只老母鸡,用的时候就下蛋,这也是生成器的特性 >>> chicken=('鸡蛋%s' %i for i in range(5)) >>> chicken <generator object <genexpr> at 0x10143f200> >>> next(chicken) '鸡蛋0' >>> list(chicken) #因chicken可迭代,因而可以转成列表 ['鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4',] #3、优点:省内存,一次只产生一个值在内存中
既然有了列表生成式,为什么还出现生成器生成式来生成列表,这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
生成器函数和普通函数之间的区别?
1.生成器函数中有yield关键字。
2.生成器函数执行后不会立即执行,而是返回一个生成器。
def func(): print("你好") yield 1 print("中国") yield 2 func() print(func())
结果:
<generator object func at 0x000002DD23F698E0> #直接执行生成器函数后形成了一个生成器
如何让它打印执行?
def func(): print("你好") yield 1 print("中国") yield 2 func() g=func() print(g.__next__()) #生成器就是迭代器。 print(g.__next__())
结果:
你好 1 中国 2