Python学习日志——深入迭代(生成器)

凡是可作用于for循环的对象都是Iterable类型;

凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

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

1 list1 = range(1,99)
2 iter_list = iter(list1)
3 print(iter_list)
4 for i in range(1,10):
5     print(next(iter_list))

输出:

<range_iterator object at 0x0050B578>
1
2
3
4
5
6
7
8
9

 可见iter_list 是一个iterator对象,并成功用next()获得对象下一个值。

再看如下代码: 1 next(range(1,99)) 

输出报错:

TypeError: 'range' object is not an iterator

 可见非iterator对象是无法使用next获得值。

这里要开始说生成器generator

对于有大量元素的可迭代对象,并且是有规律对象,可不必一开始就初始化所有元素,特别是对于不知道需要多少的元素原素的可迭代对象时,这样每次计算生成新元素就可以节省大量的空间。

那么生成器怎么用呢,首先generator是一个可迭代对象,所以本身可以通过for迭代。

除此之外,再看看生成器的概念,是每次获取时候进行一次计算,计算得值进行返回,所以我们一开始是不知道生成器后面元素的,但是可以一次一次的获取,这么听起来是不是有点像迭代器。生成器,其实也是迭代器。

第一种生成器获得方法。

可以列表生成式获得,使用()包裹住生成式便得到一个generator。现在做一个简单生成器。

1 generator1 = (x for x in range(1, 99))
2 print(generator1)
3 for i in range(1,9):
4     print(next(generator1))

输出:

<generator object <genexpr> at 0x0045E090>
1
2
3
4
5
6
7
8

 可见generator就是iterator。

之前有说过列表生成式应当是:function(x) for x in range(firstIndex,endIndex),要是生成规则较为复杂,即可写匿名函数或者另外定义函数,根据每次传进来的参数,获得新的迭代值,并且生成式是可以写成多重值迭代生成形势,所以用这种方式做的生成器适用范围非常广,这里就不详细举例。
第二种生成器创建方式涉及协程的运用。

以下是一个斐波那契数列生成器

 1 def fib():
 2     n, a, b = 0, 0, 1
 3     while True:
 4         yield b
 5         a, b = b, a + b
 6         n = n + 1
 7 
 8 gen_fib = fib()
 9 print(gen_fib)
10 
11 for i in range(1, 9):
12     print(next(gen_fib))

输出:

<generator object fib at 0x0068E090>
1
1
2
3
5
8
13
21

 这里涉及协程的概念,当第一次使用next时,生成器会从头执行,遇上yield b时,将b值返回,并且这个生成器对象会停止运行并挂起,再次调用时,会从挂起的位置继续执行知道函数停止或者遇上写一个yield进行挂起。当遇上返回值时会报错:StopIteration

这两种生成器的差异,使用列表生成式时,要是每次有自定义函数,相当于获得的值每次都是从函数中获得,迭代只是决定调用函数的参数。而上面例子中使用协程生成的生成器,是在一个生成器中对数据进行迭代,只有在创建时候可以决定参数,每一次获得值时候都是在生成器本身内部进行反复迭代。

如此看来,似乎使用列表生成式生成的生成器适用范围更广,但是要是对于对于多值迭代的生成式,维护难度却会提高很多,没有写成函数形式的生成器清晰易维护。

其实对于协程迭代形式的生成器,也可以每次获得下一个值时候传递参数过去进行计算

 1 def gen():
 2     a = 1
 3     while True:
 4        b = yield a
 5        a = b + 1
 6 
 7 g = gen()
 8 print(next(g))
 9 
10 for i in range(1, 9):
11     print(g.send(i))

输出:

1
2
3
4
5
6
7
8
9

 上面的代码中每次调用send(i) 就可以把i的赋值给b,并得到下次迭代结果。

重点:这种形式的生成器,当第一次调用时候由于并没又yield挂起,并且没又接收值,所以用send传参会报错,可以向上诉方法中先使用next()或者直接使用无参数的send()

posted on 2018-09-06 09:48  SaltFishYe  阅读(192)  评论(0编辑  收藏  举报

导航