生成器和迭代器

Posted 每天进步一点点

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了生成器和迭代器相关的知识,希望对你有一定的参考价值。

生成器和迭代器

列表生成式

现在有这么一个需求,要将[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]这个列表中的每个元素都加1,那么需要怎么实现呢?你可能会想到这几种方式:


a = list(range(0,10))
# print(a)


# 1.二逼青年版
b = []
for i in a:
    b.append(i+1)

print(b)


# 2.普通青年版

c = a
for index,i in enumerate(c):
    a[index] += 1

print(c)


# 3.文艺青年版
d = a
d = map(lambda x:x,d)
for i in d:
    print(i)

其实还有一种写法,如下:

# 4.装逼青年版
e = a
e = [i+1 for i in range(10)]
print(e)

通过列表生成式,我们可以直接创建一个列表。但是受到内存限制,列表容量肯定是有限制的,就像是递归,最大递归深度python就对其作了限制。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅想访问的是前几个元素的话,那么后面绝大多数元素占用的空间就是浪费了。

就比如说:我们家是卖手机的,我们找到了专门制作手机的厂商,告诉他,我这个手机要1000万台,你们给我做吧,然而厂商考虑,和你说:这款手机我不知道能不能卖这么多,你看我们就1000台分一批给你生产出来,如果你还要,那么再给你生产1000台,你考虑到这也是一个问题,所以便同意了。

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

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

l = [x*x for x in range(10)]
print(l)  # l = [x*x for x in range(10)]

g = (x*x for x in range(10))
print(g)

运行结果为:
<generator object <genexpr> at 0x000001E28508FFC0>

创建l和g的却别仅在于最外层的[]和(),l是一个list,而g是一个generator

我们可以直接打印出来l列表中的每一个元素,但是怎么打印出generator的每一个元素呢?

如果需要打印的话,我们就需要使用next()函数获得generator的下一个返回值:

g = (x*x for x in range(10))
print(g)
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))

运行结果为:
<generator object <genexpr> at 0x000002586EF82518>
0
1
4
9
16

我们讲过,generator保存的是算法,每次调用next(g)后就能计算出g的下一个元素的值,直到计算出最后一个元素,没有更多的元素时,会抛出StopIteration异常

当然,每需要获取下一个元素的值,就需要使用一次next的话,就有点太变态了,所以,正确的方法时for循环,因为generator也是可迭代的

g = (x*x for x in range(10))
for i in g:
    print(i)
    
运行结果为:
0
1
4
9
16
25
36
49
64
81

genera足够强大,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现,著名的斐波那契数列用列表生成式写不出来,但是用函数把它打印出来却很容易:

def fib(max):
    n,a,b = 0,1,1  # 分别赋值
    while n < max:  # 条件判断
        print(b)  
        a,b = b,a+b  # 赋值,把b赋值给a,把a+b赋值给b,在这里需要注意的是,是相加之前的结果,不是把b赋值给a后,然后再相加
        n += 1
    return ‘Done‘

fib(10)

运行结果如下:
1
2
3
5
8
13
21
34
55
89

仔细观察:可以看出,fib函数是定义了斐波那契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

也就是说,上面的函数和generator只有一步之遥。要把fib函数编程生成器generator,只需要把print(b)改成yield b就可以了:

def fib(max):
    n,a,b = 0,1,1
    while n < max:
        yield b
        # 出现了yield就相当于把这段程序变成了生成器
        # yield把值返回到外部,而且不用终止
        # yield把函数的执行过程冻结到这一步,并且把b的值返回给外面的next()
        a,b = b,a+b
        n += 1
        
    return ‘Done‘

f = fib(10)
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))

运行结果如下
1
2
3
5
8
13

想要获取下一个元素的值,用next就可以了

这里最难理解的是generator和函数的执行顺序流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回(结束),而编程generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次被next()调用时从上次返回的yield语句出继续执行。

def fib(max):
    n,a,b = 0,1,1
    while n < max:
        yield b
        a,b = b,a+b
        n += 1

    return ‘Done‘
data = fib(10)
print(data)

print(data.__next__())
print(‘干点别的事‘)
print(data.__next__())

运行结果如下:
<generator object fib at 0x0000020D5B02FFC0>
1
干点别的事
2

在上面的fib例子中,我们在循环过程中不断调用yield,就会不断中断,当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。同样的,把函数改成generator后,我们都是使用for循环来获取返回值

def fib(max):
    n,a,b = 0,1,1
    while n < max:
        yield b
        a,b = b,a+b
        n += 1

    return ‘Done‘
data = fib(10)
for i in data:
    print(i)
    
运行结果如下:
1
2
3
5
8
13
21
34
55
89

但是我们发现了,如果使用for循环的话,根本拿不到generator的return语句返回值。所以如果想拿到返回的值的话,必须要捕捉异常

def fib(max):
    n,a,b = 0,1,1
    while n < max:
        yield b
        a,b = b,a+b
        n += 1

    return ‘Done‘
data = fib(10)
while True:
    try:
        x = next(data)
        print(‘data:‘,x)

    except StopIteration as e:
        print(‘Generator return value:‘,e.value)
        break
        
运行结果:
data: 1
data: 2
data: 3
data: 5
data: 8
data: 13
data: 21
data: 34
data: 55
data: 89

迭代器

我们已经直到,可以直接作用域for循环的数据类型有以下几种:

一类是集合数据类型,如list、tuple、dict、set、str等
另一类是generator,包括生成器和带yield 的 generator fgunction等

这些可以直接作用于for循环的对象统称为可迭代对象:Iterable

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

In [1]: from  collections import Iterable



In [2]: isinstance([],Iterable)
In [2]: isinstance([],Iterable)
Out[2]: True

In [3]: isinstance((),Iterable)
Out[3]: True

In [4]: isinstance({},Iterable)
Out[4]: True

In [6]: isinstance(‘abc‘,Iterable)
Out[6]: True

In [7]: isinstance((x for x in range(10)),Iterable)
Out[7]: True

而生成器不但可以作用域for循环,还可以被next()函数不断调用并返回下一个值,直到抛出错误

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

In [1]: from collections import Iterator

In [2]: isinstance((),Iterator)
Out[2]: False

In [3]: isinstance([],Iterator)
Out[3]: False

In [4]: isinstance({},Iterator)
Out[4]: False

In [5]: isinstance((x for x in range(10)),Iterator)
Out[5]: True

生成器都是迭代器,但列表、元组、字典虽然是可迭代对象,但不是迭代器

把列表、元组、字典等可迭代对象转换成迭代器可以使用iter()函数

In [6]: isinstance(iter([]),Iterator)
Out[6]: True

In [7]: isinstance(iter({}),Iterator)
Out[7]: True

你可能会问,为什么List,tuole,dict等数据类型不是迭代器呢?

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

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

以上是关于生成器和迭代器的主要内容,如果未能解决你的问题,请参考以下文章

Interator和Generator

Interator和Generator

代码详解生成器迭代器

迭代器和生成器

python函数:迭代器和生成器

Python中迭代器和生成器的区别与联系