Python笔记(七):生成器与迭代器


列表生成式:

[i*2 for i in range(10)]

>> [0,2,4,6,8,10,12,14,16,18]

生成器

通过列表生成式可以直接创建一个列表,但当列表元素很多时,会占用很大的存储空间;所以,如果可以将列表元素按照某种算法推算出来,再循环过程中不断推算后续的元素,这样就不用创建完整的list ,节省了大量的空间,这种一边循环一变计算的机制,称为:生成器(generator)

生成器的创建

创建一个generator很简单,只需要将生成式的 [] 改为 () 即可:

[i*2 for i in range(10)]
(i*2 for i in range(10))

>> [0,2,4,6,8,10,12,14,16,18]
>> <generator object <genexpr> at 0x000000000580ABF8>

使用生成器此时,并未直接创建出列表,但如果使用循环调用这些数据:

a = (i*2 for i in range(10))
for i in a:
    print(i)
>> 0
>> 2
>> 4
>> 6
>> ...
>> 18

#但如果直接调用某一位的数据:
a[3]
>> 出错

虽然开始时列表数据未被创建,但使用循环顺序调用时,数据都会生成出来;
如果不是顺序,而是直接调用某一位的数据,则此时会出错,因为它根本就没有被创建。

但是,如果推算的算法非常复杂,用for循环无法实现的时候,还可以用函数来实现,比如:斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
- 斐波拉契数列无法用列表生成式写出来,但可以用函数打印出来:

def fib(max):
    n,a,b = 0,0,1
    while n < max:
        #print(b)
        yield b
        a,b = b,a+b
        n = n+1
    return "done"         #调用超出时,会返回done异常

f = fib(100)
print(f)
print(f.__next__())        #依次打印
print(f.__next__())
print(f.__next__())
#每次得到一个值后,函数便停在yield处,此时可以转做别的事情,然后再回来还可以继续生成

>> <generator object fib at 0x00000000022B80F8>
>> 1
>> 1
>> 2

上述函数定义了 斐波拉契数列 的推算规则,从第一个元素开始,推出后续任意的元素
只需将print() 改为 yield 即变成生成器;

def fib(max):
    n,a,b = 0,0,1
    while n < max:
        #print(b)
        yield b
        a,b = b,a+b
        n = n+1
    return "done"
g = fib(10)
while True:
    try:
        x = next(g)
        print("g:",x)
    except StopIteration as e:
        print("Generator return value:", e.value)
        break
>> g: 1
   g: 1
   g: 2
   g: 3
   g: 5
   g: 8
   g: 13
   g: 21
   g: 34
   g: 55
   Generator return value: done

程序执行过程
- **1、**While True 中执行try,调用 next ,则回到函数fib (next即取一位)
- 2、走到yield后,则中断,将b = 1的值带回来传给 x;(x=1),打印g:


- 3、然后再次执行try, 调用next ,再次回到函数fib
- 4、回到函数fib后,将从yield下一句继续执行
- 5、函数执行完(n = n+1执行完)后,再次跳回 yield
- 6、此时遇到 yield ,再次中断,回到打印g:

yield:相当于保存了此时函数的值。



生成器特点
- 生成器只有在调用的时候才会产生相应的数据(只记住当前位置)
- 取值只能一个一个的取

生成器并行

函数执行了一半,停下来,可以做些别的事情,执行一会儿再回来,单线程也可以并发;

例:一个典型的生产者与消费者模型

import time
def consumer(name):
    print("%s 准备吃包子了!" %name)
    while True:
        baozi = yield
        print("包子[%s]来了,被[%s]吃了" %(baozi,name))

c = consumer("Evan")
c.__next__()            #此时程序走到yield返回,后面的未执行


# c.send(b1)             #把b1的值传到了yield ,赋值给了包子 。与__next__的区别是next只是调用yield,send调用yield并给它传值
# c.__next__()


def producer(name):
    c = consumer("A")
    c2 = consumer("B")
    c.__next__()
    c2.__next__()
    print("我要开始做包子了!")
    for i in range(10):
        time.sleep(1)
        print("做了一个包子,分两半")
        c.send(i)
        c2.send(i)

producer("Evan")

send和_ _ next _ _()的区别在于:
- next只是调用yield
- send调用yield并给它传值

程序运行流程可以打断点仔细看一遍;

迭代器

可直接作用于for循环的数据类型有:
- 数据集合类型:如 list、tuple、dict、set、srt等;
- generator,包括生成器和带yield的generator function

这些可直接作用于for循环的对象统称为可迭代对象(Iterable),简单理解就是可循环的对象

判断:可以使用isinstance()来判断一个对象是否是Iterable对象

from collections import Iterable
isinstance([],Iterable)    #判断列表是否为可迭代对象
>>True

迭代器和可迭代对象不是一回事

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator

例如:

a = [1,2,3]
dir(a)
>>  #一系列中没有__next__操作

from collections import Iterable  #可迭代
isinstance(a,Iterable)
>True

from collections import Iterator   #迭代器
isinstance(a,Iterator) 
>>False

注意差别,可迭代和迭代器的差别
a 是一个可迭代对象,但 a 不是一个迭代器;

使用dir命令可以查看a的所有可以执行的操作


那如何将可迭代对象变成迭代器呢?
- 可以使用iter()函数

a = [1,2,3]
b = iter(a)
print(b)
b.__next__()
b.__next__()
isinstance(a,Iterator) 

>> <list_irerator object at 0x000000000595EC18>
>> 1
>> 2
>> True

Iterator对象表示的是一个数据流,按需计算下一个数据,只有在需要返回下一个数据时才会进行计算,可以表示一个无限大的数据流;

猜你喜欢

转载自blog.csdn.net/weixin_42026630/article/details/80424984