Python—迭代器与生成器

本文深入探讨了Python中的迭代器与生成器的概念及其应用,包括列表生成器、斐波那契数列生成器的实现,以及如何使用yield关键字。同时,文章对比了迭代器与生成器的区别,并介绍了如何通过迭代器协议实现惰性计算。

迭代器与生成器

 

生成器(generator)


 

先来了解一下列表生成器:

1 list = [i*2 for i in range(10)]   
2 print(list)
>>>>
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

gen1 = (i*2 for i in range(10))
print(gen1)
>>>>>
<generator object <genexpr> at 0x0000022419B21AF0>  #列表生成器打印出来只是一个内存地址

要注意的是:

1.打印生成器,只是打印其内存地址,生成器只有在调用的时候,才会产生元素,只能一个个取值

 

2.生成器不能像列表一样访问某个元素,或者切片。只能通过for循环打印出来,或者通过 【__next__()】,括号里不能给参数  2.7里 是 next()

 

3.生成器只有一个 __next__() 方法,生成器只会记住当前的取值,可以用next方法调用下一个,但是不能往前,内置函数 next也可以调用,for循环也可以调用,还可以数据类型强制转换: list(generator)

 >在一次运行过程中,生成器遍历取值完就没有值了。 迭代器也是遍历完就没有值了,同样也会有 StopIteration Error

>生成器是一类特殊的迭代器。在函数中用 yield 

#for循环调用生成器,,yield不能和return共用,且要写在函数内部
>>> def generator(): print(1) yield 33333 #可以把yield看成return一个值,但是不结束函数,只是暂时中断 print(2) yield 44444 >>> g = generator() #此时g就是一个生成器,generator就是iterator,所以可以用for循环 >>> for i in g: print(i) ----> 1 33333 2 44444 >>>

 

用函数生成生成器,以斐波那契数列进行举例(yield不能和return共用,且要写在函数内部)

 1 def fibo(max):
 2     n,a,b = 0,0,1
 3     while n<max:
 4         #print(b)
 5         yield  b       #生成器的创建    yield:(返回后暂停)保持当前状态并中断函数,下次运行时,从这里往后运行,因为保存了当前状态
 6         a,b = b,a+b    #相当于 t =(b,a+b)   a = t[0]  b = [t1]
 7         n +=1
 8     return 'done'
 9 
10 f = fibo(10)
11 print(f.__next__())
12 print(f.__next__())
13 print('----做点别的事情----')   #生成器可以调用一下,然后停下来做别的事,其他函数会一口气打印出所有结果
14 print(f.__next__())
15 print(f.__next__())
16 print('----开始for循环----')   #__next__方法只记录当前位置
17 for i in f:
18     print(i)
19 
20 >>>>>#结果如下所示
21 1
22 1
23 ----做点别的事情----
24 2
25 3
26 ----开始for循环----
27 5
28 8
29 13
30 21
31 34
32 55

 

StopIteration异常:
def fibo(max):
    n,a,b = 0,0,1
    while n<max:
        #print(b)
        yield  b
        a,b = b,a+b    #相当于 t =(b,a+b)   a = t[0]  b = [t1]
        n +=1
    return '----done----'

f = fibo(3)             #只运行3次斐波那契数列
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())   #此时调用了4次next方法  ,此时会报 StopIteration错误

>>>>>> Traceback (most recent call last): 1 File "C:/Users/15302/PycharmProjects/GKXXX/day3/斐波那契数列.py", line 20, in <module> 1 print(f.__next__()) 2 StopIteration: ----done---- #这个done是函数返回值

可以用【try—except】来抓住异常(for循环用的就是这种机制)

def fibo(max):
    ‘--snip--’

f = fibo(5)
while True:                #用 try—except 来抓住异常
    try:
        x = next(f)
        print('斐波那契数列:',x)   #打印每次运行generator的值
    except StopIteration as e:  #抓住StopIteration异常
        print('Generator return value:',e.value)  #输出返回值
        break

>>>>>
斐波那契数列: 1
斐波那契数列: 1
斐波那契数列: 2
斐波那契数列: 3
斐波那契数列: 5
Generator return value: ----done----

 

关于yield(yield类似return,如果不打印是不显示yield的值的,只会执行程序)

def gen():
    print('start')
    m = yield 2  # 可以看作yield返回值为2,send(3)把3传递给m,并调用,m变成3了,同下
    print(m)
    n = yield 3
    print(n)

try:
    g = gen()     #此时不运行gen()函数,若print(g) 会打印该生成器的内存地址
    g.send(None)  # 相当于 g.__next__()   此处若 print(g.send(None)) 则先执行 打印start,然后执行 打印 yiled的返回值 2,然后函数暂停
    g.send(3333)   #此处若 print(g.send(3333))  首先函数继续往下走,先把send里的3333赋值给m,然后打印m,接下来执行 yield 3的返回值,打印3
    g.send(6666)    
except StopIteration as e:
    print(e.value)

>>>>
start
3333
6666
None
yield : (返回并暂停)保持当前状态并中断,下次运行从这个状态开始
send(): 给yiield传值并调用  .__next__() 相当于 .send(None)
next:调用yield,唤醒它
 
做包子练习
 1 import time
 2 def consumer(name):
 3     print("%s 准备吃包子啦!" %name)
 4     while True:
 5        baozi = yield
 6 
 7        print("包子[%s]来了,被[%s]吃了!" %(baozi,name))
 8 
 9 # c = consumer('gkx') #此时不运行程序,即当函数中有yield时候,一定要用 __next__,send()方法调用才会运行,这句话相当于把函数变成生成器而已
10 # c.__next__()        #第一次运行到 yield,然后保存当前状态,停止
11 # b1 = 'jiucai'
12 # c.send(b1)
13 #c.__next__()        #运行yield往后的语句,即第二句 print处
14 def producer(name):
15     c = consumer('A')
16     c2 = consumer('B')
17     c.__next__()
18     c2.__next__()
19     print("开始准备做包子啦!")
20     for i in range(10):
21         time.sleep(1)
22         print("做了2个包子!")
23         c.send(i)
24         c2.send(i)
25 
26 producer("gkx")
View Code

 

 

迭代器


 1.凡是可作用于for循环的对象都是Iterable类型; 可迭代协议— 只要含有 __iter__方法的都是可迭代的

 1 >>> from collections import Iterable  
 2 >>> isinstance([],Iterable)
 3 True
 4 >>> isinstance((),Iterable)
 5 True
 6 >>> isinstance({},Iterable)
 7 True
 8 >>> isinstance(‘abc’,Iterable)
 9 True
10 >>> isinstance((x for x in range(10)),Iterable)
11 True
12 >>> isinstance(100,Iterable)
13 False
14 
15 #list,set,dict,str,generatior都是可迭代的,数字不可迭代

print('__iter__' in dir([])) >>>>>简单粗暴的判断方法

 

2.凡是可作用于next()函数的对象都是Iterator(迭代器)类型,它们表示一个惰性计算的序列;(生成器一定是迭代器,迭代器不一定是生成器)

    迭代器协议:含有 __next__ 和 __iter__方法的,就是迭代器

 1 >>> from collections import Iterator
 2 >>> isinstance((x for x in range(10)),Iterator)
 3 True
 4 >>> isinstance([],Iterator)
 5 False
 6 >>> isinstance({},Iterator)
 7 False
 8 >>> isinstance('abc',Iterator)
 9 False
10 >>> 
11 
12 #在dict,set,list,str,generator中,只有generator才是迭代器

 

3.集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

from collections import Iterator
1
>>> isinstance(iter([]),Iterator) 2 True 3 >>> isinstance(iter({}),Iterator) 4 True

  >>> a = ['1','2']
  >>> iter(a)
  <list_iterator object at 0x00000262D6E85F60>
  >>> iter(a).__next__()  #把列表a转换为迭代器,可以使用__next__()函数
  '1'
  >>>

我们在文件操作中,用 for line in f:  其中 文件句柄 f 就是迭代器

 

for循环等价于:

 1 >>> it = iter(range(10))
 2 >>> while True:
 3     try:
 4         x = next(it)
 5         print(x)
 6     except StopIteration:
 7         break
 8 
 9     
10 0
11 1
12 2
13 3
14 4
15 5
16 6
17 7
18 8
19 9
20 >>> 
View Code

 

【你可能会问,为什么listdictstr等数据类型不是Iterator

这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。】

                                                  —https://www.cnblogs.com/alex3714/articles/5765046.html

 

 

 

 

 

 

 

 

 

 

转载于:https://www.cnblogs.com/gkx0731/p/9460055.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值