python基础6 迭代器 生成器

迭代器

可迭代的或迭代对象

可迭代的:内部含有__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.

我们来完整的看看迭代过程是怎么实现的:当任何可迭代对象传入到for循环或其他迭代工具中进行遍历时,迭代工具都是先通过iter函数获得与可迭代对象对应的迭代器,然后再对迭代器调用next函数,不断的依次获取元素,并在捕捉到StopIteration异常时确定完成迭代,这就是完整的迭代过程。这也称之为“迭代协议”。

作者:酱油哥

链接: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. 记住上次执行的状态
  2. 自动切换到不同任务
  3. 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