怎么确定迭代器后面还有至少两个值_生成器和迭代器总结

0fe6dc030f26c7cabbe46c7696893c69.png

生成器和迭代器这两个概念,对于以前一直用C#的我来说,刚看到这两个类型的时候确实很晕。目前为止,也只能说是一知半解把吧,远未达到熟练运用的程度。所以,最近认真研究了一下,并总结如下,如果能够帮助大家更好地理解和应用这两个类型的函数,那就善莫大焉 。

一、生成器(Generator)

  1. 什么是生成器?

(1)生成器是一种特殊的迭代器,关于迭代器后面会讲到。生成器的出现就是为了简化迭代器应用。

生成器的主要思想:对于可以公式自动生成的数字序列,由计算机不断迭代,每次只生成一个数字,从而通过循环遍历生成序列中的所有元素。所以说,生成器产生的不是一个静态的值(比如类似字符串、元组和列表等,都是一次性生成所有值),而是一个动态的数据流。看下面两个实例:

实列1:

>>> a = (x**2 for x in range(1,9))
>>> type(a)
<class 'generator'>
>>> next(a)
1 

实例2:

>>> def gen(x):
x+=1
yield x**2
>>> b = gen(0)
>>> type(b)
<class 'generator'>
>>> next(b)
1

可以看到,变量a和b都是生成器,我们不能直接使用a、b,因为它们实际上保存的是一个公式,使用时可以调用内置函数next(),由next(a)、next(b)来动态生成序列中的下一个值。采用生成器的好处是:节省内存空间,特别是对于数据量大的序列,一次性生成所有值将会耗费大量内存,而采用生成器可以极大地节省存储空间。同时,生成器还可以处理无限长的序列。比如,上述实例中,变量b就是一个无限序列,理论上可以永远next(b),而且每次都是按顺序生成其中的一个值。

(2)可以把生成器看作是一种特殊的函数,它与一般函数最主要的区别就在于生成器函数中有关键字yield。比如,上述实例2,函数中只要有yield关键字,就是一个生成器函数。

2. 生成器怎么用?

(1)生成器的使用场景

当你需要生成一个大型的序列,但又不想因此占用大量的存储空间,提高存储和计算效率。此时,可以考虑用生成器。

应用实例:在我们开发生命游戏过程中,需要主线程不断迭代来模拟生命演化过程,每次迭代过程都要遍历地图中的所有点判断其处于“生”或“死”的状态。如果一次性生成地图中所有点的坐标,将会占用大量存储空间。而地图中每个点的坐标是可以通过公式推导自动计算的,因此,可定义如下的生成器动态生成地图中每个点的坐标:

defgenerator_cell(map_width,map_height):
'''
定义一个生成器,用来生成地图中的所有细胞
:param map_width:地图宽度
:param map_height:地图长度
:return:返回地图中的细胞坐标
'''
x_cell = 0
for num_x in range(map._width):
    y_cell=0
    x_cell += cell_width
    for num_y in range(map._height):
        y_cell+=cell_height
        yield x_cell,y_cell 

(2)生成器的构造

主要有两种方式:一是生成器表达式;二是生成器函数。上面实例1就是生成器表达式;实例2其实就是生成器函数。

①利用生成器表达式构造

类似与列表生成式,只是将列表生成式中的中括号[]改为小括号()。

#列表生成式

 >>> lis = [x*x for x in range(10)]
 >>> print(lis)
 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
 #生成器表达式
 >>> generator_ex = (x*x for x in range(10))
 >>> print(generator_ex)
 <generator   object <genexpr> at  0x00000216380B1CA8>

②利用生成器函数构造

函数体中有关键字yield。yield关键字类似于return,当生成器被next()函数调用时,会返回其后的变量,相当于程序中断;当再次调用next()函数后,生成器会从中断的yield语句处继续执行,也就是用多少,取多少,不占内存。

应用实例:Fibonacci数列的生成器计算函数

>>> def fib(x):
        a, b, n= 0, 1, 1
        while n <= x:
            yield b
            a, b = b, a+ b
            n += 1
>>> list(fib(8))
[1, 1, 2, 3, 5, 8, 13, 21]

(3)生成器的使用

构造生成器后,如何使用它呢?①可以通过for循环遍历所有值;②或者用内置函数next()循环生成下一个值;③还可用生成器自身方法__next()__循环生成下一个值。

①采用for循环

比如,上面生成的generator_ex,用for循环遍历方式如下:

>>> for i in generator_ex:
        print(i)
0
1
4
9
16
25
36
49
64
81

②采用内置函数next()遍历生成器元素

>>> generator_ex = (x*x for x in range(10))
>>> next(generator_ex)
0
>>> next(generator_ex)
1
>>> next(generator_ex)
4
>>> next(generator_ex)
9
>>> next(generator_ex)
16
>>> next(generator_ex)
25
>>> next(generator_ex)
36
>>> next(generator_ex)
49
>>> next(generator_ex)
64
>>> next(generator_ex)
81
>>> next(generator_ex)
Traceback (most recent call last):
File "<pyshell#94>", line 1, in <module>
next(generator_ex)
StopIteration

③采用生成器自身方法__next()__循环生成下一个值。

>>> generator_ex = (x*x for x in range(10))
>>> generator_ex.__next__()
0
>>> generator_ex.__next__()
1
>>> generator_ex.__next__()
4
>>> generator_ex.__next__()
9
>>> generator_ex.__next__()
16
>>> generator_ex.__next__()
25
>>> generator_ex.__next__()
36
>>> generator_ex.__next__()
49
>>> generator_ex.__next__()
64
>>> generator_ex.__next__()
81
>>> generator_ex.__next__()
Traceback (most recent call last):
File "<pyshell#107>", line 1, in <module>
generator_ex.__next__()
StopIteration

可以看到,generator保存的是算法,每次调用next(generaotr_ex)就计算出他的下一个元素的值,直到计算出最后一个元素,没有更多的元素时,抛出StopIteration的错误。

注意:

①通常访问生成器元素的较常用的方法就是采用for循环,next()方法极少使用。因为采用for循环不需要关心StopIteration异常。

②内置函数next()和方法__next__()运行机制是相同的,因为内置函数next()实际上就是调用了生成器自身方法__next__()。

③生成器只能遍历一次,生成最后一个元素后,再次调用next()或__next__()会抛出StopIteration异常。

3. 总结

  • 生成器generator主要用于提高存储效率,动态生成数据流。生成器是带有yield的函数的特殊函数,可用于迭代;
  • yield是一个类似return 的关键字,迭代一次遇到yield的时候就返回yield后面或者右面的值。而且下一次迭代的时候,从上一次迭代遇到的yield后面的代码开始执行;
  • yield就是return返回的一个值,并且记住这个返回的位置。下一次迭代就从这个位置开始;
  • 带有yield的函数不仅仅是只用于for循环,而且可用于某个函数的参数,只要这个函数的参数也允许迭代参数。

二、迭代器(iterator)

  1. 什么是迭代器?

首先解释几个概念:

(1)可迭代对象。可以直接作用于for 循环的对象统称为可迭代对象:Iterable。可以使用isinstance()判断一个对象是否为可Iterable对象。

>>> from collections import Iterable
>>> isinstance(fib(8), Iterable)
True
>>> b = [1,2,3,4]
>>> isinstance(b, Iterable)
True
>>> c = 8
>>> isinstance(c, Iterable)
False

Python中可直接采用for循环的对象有:一类是集合数据类型,如list,tuple,dict,set,str等;一类是generator,包括生成器表达式和带yield的生成器函数。

(2)迭代器。Python中一个实现了_iter_方法和_next_方法的类对象,就是迭代器。

(3)迭代器协议:要构造一个迭代器,对象需要提供next()方法,它要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代。

2. 迭代器的构造

(1)自定义迭代器类

迭代器应用实例:Fibonacci数列的迭代器计算函数

class Fib(object):
    def __init__(self, max):
        super(Fib, self).__init__()
        self.max = max
    def __iter__(self):
        self.a = 0
        self.b = 1
        return self
    def __next__(self):
        fib = self.a
        if fib > self.max:
            raise StopIteration
        self.a, self.b = self.b, self.a + self.b
        return fib
# 定义一个main函数,循环遍历每一个菲波那切数
def main():
    # 20以内的数
    fib = Fib(20)
    for i in fib:
        print(i)
# 测试
if __name__ == '__main__':
    main()
注:以上迭代器实现代码参考文章: https:// blog.csdn.net/u01474519 4/article/details/70176117 (版权声明:本文为CSDN博主「ITxiaoke」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。)

可以看到,如果用迭代器,要比生成器麻烦很多。所以,生成器是一种特殊的迭代器,生成器的出现是为了简化迭代器的使用。

(2)通过调用内置函数iter()构造迭代器

比如,可迭代对象不是迭代器,若要得到迭代器:

>>> g = iter(range(10))
>>> isinstance(g, Iterator)
True
>>> isinstance(range(10), Iterable)
True
>>> isinstance(range(10), Iterator)
False

注意,不少文章中写道,map、filter等内置函数返回的都是生成器,还有个别资料中说range返回的也是生成器。要检查一个对象是否为迭代器,也可以采用isinstance()判断,所以我们可以进行以下的判断:

>>> from collections import Iterable, Iterator, Generator
>>> d= range(10)
>>> isinstance(d, Iterable)
True
>>> isinstance(d, Iterator)
False
>>> e = map(lambda x : x**2, [1,2,4,6,7,8,9])
>>> isinstance(e, Iterable)
True
>>> isinstance(e, Iterator)
True
>>> isinstance(e, Generator)
>>> f = (x**2 for x in [1,2,4,6,7,8,9])
>>> isinstance(f, Iterator)
True
>>> isinstance(f, Generator)
True

从以上的判断可以得到结论:range()函数返回的是一个可迭代对象;map()函数返回的是一个迭代器,因此也是一个可迭代对象,但不是生成器;生成器表达式生成的是一个Generator,同时也是一个迭代器。

3. 总结

  • 要构造迭代器,需要在自定义类中实现__iter__()方法和__next__()方法;或者通过内置函数iter()作用于可迭代对象来构造迭代器;
  • 生成器是一种特殊的迭代器,生成器是为了简化迭代器的使用;
  • 凡是可作用于for循环的对象都是可迭代对象;
  • 凡是可作用于next()函数的对象都是可迭代对象类型,它们表示一个惰性计算的序列;
  • 集合数据类型如list、dict、str等是可迭代对象,但不是迭代器,不过可以通过iter()函数获得一个Iterator对象。

一直想把生成器和迭代器总结一下,今天终于完成了这项工作。花了一整天时间来梳理总结,如果大家喜欢或者觉得对你还有点用,请别忘了点个赞 (*^_^*)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值