咸鱼的鱼

导航

生成器

 文章摘自:

      https://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/

      https://www.cnblogs.com/wj-1314/p/8490822.htm

您可能听说过,带有 yield 的函数在 Python 中被称之为 generator(生成器),何谓 generator ?

我们先抛开 generator,以一个常见的编程题目来展示 yield 的概念。

如何生成斐波那契数列

斐波那契(Fibonacci)數列是一个非常简单的递归数列,除第一个和第二个数外,任意一个数都可由前两个数相加得到。用计算机程序输出斐波那契數列的前 N 个数是一个非常简单的问题,许多初学者都可以轻易写出如下函数:

 清单 1. 简单输出斐波那契數列前 N 个数

 

1 def fab(max): 
2     n, a, b = 0, 0, 1 
3     while n < max: 
4         print b 
5         a, b = b, a + b 
6         n = n + 1

 

执行 fab(5),我们可以得到如下输出:

1 >>>fab(5)
2 1
3 1
4 2
5 3
6 5

结果没有问题,但有经验的开发者会指出,直接在 fab 函数中用 print 打印数字会导致该函数可复用性较差,因为 fab 函数返回 None(return,return None,不写return),其他函数无法获得该函数生成的数列。  要提高 fab 函数的可复用性,最好不要直接打印出数列,而是返回一个 List。以下是 fab 函数改写后的第二个版本:

 清单 2. 输出斐波那契數列前 N 个数第二版

 

1 def fab(max): 
2     n, a, b = 0, 0, 1 
3     L = [] 
4     while n < max: 
5         L.append(b) 
6         a, b = b, a + b 
7         n = n + 1 
8     return L

 可以使用如下方式打印出 fab 函数返回的 List:

1 >>> for n in fab(5): 
2 ...     print n 
3 ... 
4 1 
5 1 
6 2 
7 3 
8 5

改写后的 fab 函数通过返回 List 能满足复用性的要求,但是更有经验的开发者会指出,该函数在运行中占用的内存会随着参数 max 的增大而增大,如果要控制内存占用,最好不要用 List来保存中间结果,而是通过 iterable 对象来迭代。例如,在 Python2.x 中,代码:

清单3.通过 iterable 对象来迭代

1 for i in range(1000): pass

会导致生成一个 1000 个元素的 List,而代码:

1 for i in xrange(1000): pass

则不会生成一个 1000 个元素的 List,而是在每次迭代中返回下一个数值,内存空间占用很小。因为 xrange 不返回 List,而是返回一个 iterable 对象。

利用 iterable 我们可以把 fab 函数改写为一个支持 iterable 的 class,以下是第三个版本的 Fab:

 

清单 4. 第三个版本

 1 class Fab(object): 
 2  
 3    def __init__(self, max): 
 4        self.max = max 
 5        self.n, self.a, self.b = 0, 0, 1 
 6  
 7    def __iter__(self): 
 8        return self 
 9  
10    def next(self): 
11        if self.n < self.max: 
12            r = self.b 
13            self.a, self.b = self.b, self.a + self.b 
14            self.n = self.n + 1 
15            return r 
16        raise StopIteration()

Fab 类通过 next() 不断返回数列的下一个数,内存占用始终为常数

 

1 >>> for n in Fab(5): 
2 ...     print n 
3 ... 
4 1 
5 1 
6 2 
7 3 
8 5

 

然而,使用 class 改写的这个版本,代码远远没有第一版的 fab 函数来得简洁。如果我们想要保持第一版 fab 函数的简洁性,同时又要获得 iterable 的效果,yield 就派上用场了:

清单 5. 使用 yield 的第四版

1 def fab(max): 
2     n, a, b = 0, 0, 1 
3     while n < max: 
4         yield b 
5         # print b 
6         a, b = b, a + b 
7         n = n + 1 
8  
9 '''

第四个版本的 fab 和第一版相比,仅仅把 print b 改为了 yield b,就在保持简洁性的同时获得了 iterable 的效果。

调用第四版的 fab 和第二版的 fab 完全一致

1 >>> for n in fab(5): 
2 ...     print n 
3 ... 
4 1 
5 1 
6 2 
7 3 

简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(5) 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。

也可以手动调用 fab(5) 的 next() 方法(因为 fab(5) 是一个 generator 对象,该对象具有 next() 方法),这样我们就可以更清楚地看到 fab 的执行流程:

 

清单 6. 执行流程

 1 >>> f = fab(5) 
 2 >>> f.next() 
 3 1 
 4 >>> f.next() 
 5 1 
 6 >>> f.next() 
 7 2 
 8 >>> f.next() 
 9 3 
10 >>> f.next() 
11 5 
12 >>> f.next() 
13 Traceback (most recent call last): 
14  File "<stdin>", line 1, in <module> 
15 StopIteration 

当函数执行结束时,generator 自动抛出 StopIteration 异常,表示迭代完成。在 for 循环里,无需处理 StopIteration 异常,循环会正常结束。

我们可以得出以下结论:

一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。

yield 的好处是显而易见的,把一个函数改写为一个 generator 就获得了迭代能力,比起用类的实例保存状态来计算下一个 next() 的值,不仅代码简洁,而且执行流程异常清晰。

如何判断一个函数是否是一个特殊的 generator 函数?可以利用 isgeneratorfunction 判断:

清单 7. 使用 isgeneratorfunction 判断

 

1 >>> from inspect import isgeneratorfunction 
2 >>> isgeneratorfunction(fab) 
3 True

 

要注意区分 fab 和 fab(5),fab 是一个 generator function,而 fab(5) 是调用 fab 返回的一个 generator,好比类的定义和类的实例的区别:

 

清单 8. 类的定义和类的实例

1 >>> import types 
2 >>> isinstance(fab, types.GeneratorType) 
3 False 
4 >>> isinstance(fab(5), types.GeneratorType) 
5 True

fab 是无法迭代的,而 fab(5) 是可迭代的:

 

1 >>> from collections import Iterable 
2 >>> isinstance(fab, Iterable) 
3 False 
4 >>> isinstance(fab(5), Iterable) 
5 True

 

每次调用 fab 函数都会生成一个新的 generator 实例,各实例互不影响:

 

 1 >>> f1 = fab(3) 
 2 >>> f2 = fab(5) 
 3 >>> print 'f1:', f1.next() 
 4 f1: 1 
 5 >>> print 'f2:', f2.next() 
 6 f2: 1 
 7 >>> print 'f1:', f1.next() 
 8 f1: 1 
 9 >>> print 'f2:', f2.next() 
10 f2: 1 
11 >>> print 'f1:', f1.next() 
12 f1: 2 
13 >>> print 'f2:', f2.next() 
14 f2: 2 
15 >>> print 'f2:', f2.next() 
16 f2: 3 
17 >>> print 'f2:', f2.next() 
18 f2: 5

 

return 的作用

在一个 generator function 中,如果没有 return,则默认执行至函数完毕,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。

 

另一个例子

另一个 yield 的例子来源于文件读取。如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取:

 

清单 9. 另一个 yield 的例子

1 def read_file(fpath): 
2    BLOCK_SIZE = 1024 
3    with open(fpath, 'rb') as f: 
4        while True: 
5            block = f.read(BLOCK_SIZE) 
6            if block: 
7                yield block 
8            else: 
9                return

以上仅仅简单介绍了 yield 的基本概念和用法,yield 在 Python 3 中还有更强大的用法,我们会在后续文章中讨论。

注:本文的代码均在 Python 2.7 中调试通过

 

什么是生成器?

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

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

  生成器是一个特殊的程序,可以被用作控制循环的迭代行为,python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器。

  生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用,但是,不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快的处理前几个返回值,因此生成器看起来像是一个函数,但是表现得却像是迭代器

  

python中的生成器

  要创建一个generator,有很多种方法,第一种方法很简单,只有把一个列表生成式的[]中括号改为()小括号,就创建一个generator

 1 #列表生成式
 2 lis = [x*x for x in range(10)]
 3 print(lis)
 4 #生成器
 5 generator_ex = (x*x for x in range(10))
 6 print(generator_ex)
 7  
 8 结果:
 9 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
10 <generator object <genexpr> at 0x000002A4CBF9EBA0>

  那么创建lis和generator_ex,的区别是什么呢?从表面看就是[  ]和(),但是结果却不一样,一个打印出来是列表(因为是列表生成式),而第二个打印出来却是<generator object <genexpr> at 0x000002A4CBF9EBA0>,那么如何打印出来generator_ex的每一个元素呢?

  如果要一个个打印出来,可以通过next()函数获得generator的下一个返回值:

 

 1 #生成器
 2 generator_ex = (x*x for x in range(10))
 3 print(next(generator_ex))
 4 print(next(generator_ex))
 5 print(next(generator_ex))
 6 print(next(generator_ex))
 7 print(next(generator_ex))
 8 print(next(generator_ex))
 9 print(next(generator_ex))
10 print(next(generator_ex))
11 print(next(generator_ex))
12 print(next(generator_ex))
13 print(next(generator_ex))
14 结果:
15 0
16 1
17 4
18 9
19 16
20 25
21 36
22 49
23 64
24 81
25 Traceback (most recent call last):
26  
27   File "列表生成式.py", line 42, in <module>
28  
29     print(next(generator_ex))
30  
31 StopIteration

 

可以看到,generator保存的是算法,每次调用next(generaotr_ex)就计算出他的下一个元素的值,直到计算出最后一个元素,没有更多的元素时,抛出StopIteration的错误,而且上面这样不断调用是一个不好的习惯,正确的方法是使用for循环,因为generator也是可迭代对象:

 

 1 #生成器
 2 generator_ex = (x*x for x in range(10))
 3 for i in generator_ex:
 4     print(i)
 5      
 6 结果:
 7 0
 8 1
 9 4
10 9
11 16
12 25
13 36
14 49
15 64
16 81

 

所以我们创建一个generator后,基本上永远不会调用next(),而是通过for循环来迭代,并且不需要关心StopIteration的错误,generator非常强大,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

比如著名的斐波那契数列,除第一个和第二个数外,任何一个数都可以由前两个相加得到:

1,1,2,3,5,8,12,21,34.....

斐波那契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

 

 1 #fibonacci数列
 2 def fib(max):
 3     n,a,b =0,0,1
 4     while n < max:
 5         a,b =b,a+b
 6         n = n+1
 7         print(a)
 8     return 'done'
 9  
10 a = fib(10)
11 print(fib(10))

 

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

也就是说上面的函数也可以用generator来实现,上面我们发现,print(b)每次函数运行都要打印,占内存,所以为了不占内存,我们也可以使用生成器,这里叫yield。如下:

 

 1 def fib(max):
 2     n,a,b =0,0,1
 3     while n < max:
 4         yield b
 5         a,b =b,a+b
 6         n = n+1
 7     return 'done'
 8  
 9 a = fib(10)
10 print(fib(10))

 

但是返回的不再是一个值,而是一个生成器:

 

1 <generator object fib at 0x000001C03AC34FC0>

 

那么这样就不占内存了,这里说一下generator和函数的执行流程,函数是顺序执行的,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次被next()调用时,从上次的返回yield语句处执行,也就是用多少,取多少,不占内存。

 

 1 def fib(max):
 2     n,a,b =0,0,1
 3     while n < max:
 4         yield b
 5         a,b =b,a+b
 6         n = n+1
 7     return 'done'
 8  
 9 a = fib(10)
10 print(fib(10))
11 print(a.__next__())
12 print(a.__next__())
13 print(a.__next__())
14 print("可以顺便干其他事情")
15 print(a.__next__())
16 print(a.__next__())
17  
18 结果:
19 <generator object fib at 0x0000023A21A34FC0>
20 1
21 1
22 2
23 可以顺便干其他事情
24 3
25 5

 

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

 

 1 def fib(max):
 2     n,a,b =0,0,1
 3     while n < max:
 4         yield b
 5         a,b =b,a+b
 6         n = n+1
 7     return 'done'
 8 for i in fib(6):
 9     print(i)
10      
11 结果:
12 1
13 1
14 2
15 3
16 5
17 8

 

但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果拿不到返回值,那么就会报错,所以为了不让报错,就要进行异常处理,拿到返回值,如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:

 

 1 def fib(max):
 2     n,a,b =0,0,1
 3     while n < max:
 4         yield b
 5         a,b =b,a+b
 6         n = n+1
 7     return 'done'
 8 g = fib(6)
 9 while True:
10     try:
11         x = next(g)
12         print('generator: ',x)
13     except StopIteration as e:
14         print("生成器返回值:",e.value)
15         break
16  
17  
18 结果:
19 generator:  1
20 generator:  1
21 generator:  2
22 generator:  3
23 generator:  5
24 generator:  8
25 生成器返回值: done

 

还可以通过yield实现在单线程的情况下实现并发运算的效果

  

 1 import time
 2 
 3 
 4 def consumer(name):
 5     print("%s 准备学习啦!" % name)
 6     while True:
 7         lesson = yield
 8 
 9         print("开始[%s]了,[%s]老师来讲课了!" % (lesson, name))
10 
11 
12 def producer():
13     c = consumer('A')
14     print(c)
15     c2 = consumer('B')
16     print(c2)
17     a1 = c.__next__()
18     print(a1)
19     a2 = c2.__next__()
20     print(a2)
21     print("同学们开始上课 了!")
22     for i in range(10):
23         time.sleep(1)
24         print("到了两个同学!")
25         c.send(i)
26         c2.send(i)
27 producer()
28  
29 结果:
30 <generator object consumer at 0x0000025046DE37C8>
31 <generator object consumer at 0x000002504FBF6228>
32 A 准备学习啦!
33 None
34 B 准备学习啦!
35 None
36 同学们开始上课 了!
37 到了两个同学!
38 开始[0]了,[A]老师来讲课了!
39 开始[0]了,[B]老师来讲课了!
40 到了两个同学!
41 开始[1]了,[A]老师来讲课了!
42 开始[1]了,[B]老师来讲课了!
43 到了两个同学!
44 开始[2]了,[A]老师来讲课了!
45 开始[2]了,[B]老师来讲课了!
46 到了两个同学!
47 开始[3]了,[A]老师来讲课了!
48 开始[3]了,[B]老师来讲课了!
49 到了两个同学!
50 开始[4]了,[A]老师来讲课了!
51 开始[4]了,[B]老师来讲课了!
52 到了两个同学!
53 开始[5]了,[A]老师来讲课了!
54 开始[5]了,[B]老师来讲课了!
55 到了两个同学!
56 开始[6]了,[A]老师来讲课了!
57 开始[6]了,[B]老师来讲课了!
58 到了两个同学!
59 开始[7]了,[A]老师来讲课了!
60 开始[7]了,[B]老师来讲课了!
61 到了两个同学!
62 开始[8]了,[A]老师来讲课了!
63 开始[8]了,[B]老师来讲课了!
64 到了两个同学!
65 开始[9]了,[A]老师来讲课了!
66 开始[9]了,[B]老师来讲课了!

 

由上面的例子我么可以发现,python提供了两种基本的方式

   生成器函数:也是用def定义的,利用关键字yield一次性返回一个结果,阻塞,重新开始

   生成器表达式:返回一个对象,这个对象只有在需要的时候才产生结果

——生成器函数

为什么叫生成器函数?因为它随着时间的推移生成了一个数值队列。一般的函数在执行完毕之后会返回一个值然后退出,但是生成器函数会自动挂起,然后重新拾起急需执行,他会利用yield关键字关起函数,给调用者返回一个值,同时保留了当前的足够多的状态,可以使函数继续执行,生成器和迭代协议是密切相关的,迭代器都有一个__next__()__成员方法,这个方法要么返回迭代的下一项,要买引起异常结束迭代。

 

 1 # 函数有了yield之后,函数名+()就变成了生成器
 2 # return在生成器中代表生成器的中止,直接报错
 3 # next的作用是唤醒并继续执行
 4 # send的作用是唤醒并继续执行,发送一个信息到生成器内部
 5 '''生成器'''
 6  
 7 def create_counter(n):
 8     print("create_counter")
 9     while True:
10         yield n
11         print("increment n")
12         n +=1
13  
14 gen = create_counter(2)
15 print(gen)
16 print(next(gen))
17 print(next(gen))
18  
19 结果:
20 <generator object create_counter at 0x0000023A1694A938>
21 create_counter
22 2
23 increment n
24 3
25 Process finished with exit code 

 

——生成器表达式

生成器表达式来源于迭代和列表解析的组合,生成器和列表解析类似,但是它使用尖括号而不是方括号

 

 1 >>> # 列表解析生成列表
 2 >>> [ x ** 3 for x in range(5)]
 3 [0, 1, 8, 27, 64]
 4 >>>
 5 >>> # 生成器表达式
 6 >>> (x ** 3 for x in range(5))
 7 <generator object <genexpr> at 0x000000000315F678>
 8 >>> # 两者之间转换
 9 >>> list(x ** 3 for x in range(5))
10 [0, 1, 8, 27, 64]

 

一个迭代既可以被写成生成器函数,也可以被协程生成器表达式,均支持自动和手动迭代。而且这些生成器只支持一个active迭代,也就是说生成器的迭代器就是生成器本身。

迭代器(迭代就是循环)

  迭代器包含有next方法的实现,在正确的范围内返回期待的数据以及超出范围后能够抛出StopIteration的错误停止迭代。

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

一类是集合数据类型,如list,tuple,dict,set,str等

一类是generator,包括生成器和带yield的generator function

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

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

 1 >>> from collections import Iterable
 2 >>> isinstance([], Iterable)
 3 True
 4 >>> isinstance({}, Iterable)
 5 True
 6 >>> isinstance('abc', Iterable)
 7 True
 8 >>> isinstance((x for x in range(10)), Iterable)
 9 True
10 >>> isinstance(100, Iterable)
11 False

 而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。

所以这里讲一下迭代器

一个实现了iter方法的对象时可迭代的,一个实现next方法的对象是迭代器

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

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

 

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

 

生成器都是Iterator对象,但listdictstr虽然是Iterable(可迭代对象),却不是Iterator(迭代器)

listdictstrIterable变成Iterator可以使用iter()函数

>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True

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

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

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

  判断下列数据类型是可迭代对象or迭代器

1 s='hello'
2 l=[1,2,3,4]
3 t=(1,2,3)
4 d={'a':1}
5 set={1,2,3}
6 f=open('a.txt')
 1 s='hello'     #字符串是可迭代对象,但不是迭代器
 2 l=[1,2,3,4]     #列表是可迭代对象,但不是迭代器
 3 t=(1,2,3)       #元组是可迭代对象,但不是迭代器
 4 d={'a':1}        #字典是可迭代对象,但不是迭代器
 5 set={1,2,3}     #集合是可迭代对象,但不是迭代器
 6 # *************************************
 7 f=open('test.txt') #文件是可迭代对象,是迭代器
 8  
 9 #如何判断是可迭代对象,只有__iter__方法,执行该方法得到的迭代器对象。
10 # 及可迭代对象通过__iter__转成迭代器对象
11 from collections import Iterator  #迭代器
12 from collections import Iterable  #可迭代对象
13  
14 print(isinstance(s,Iterator))     #判断是不是迭代器
15 print(isinstance(s,Iterable))       #判断是不是可迭代对象
16  
17 #把可迭代对象转换为迭代器
18 print(isinstance(iter(s),Iterator))

注意:文件的判断

1 f = open('housing.csv')
2 from collections import Iterator
3 from collections import Iterable
4  
5 print(isinstance(f,Iterator))
6 print(isinstance(f,Iterable))
7  
8 True
9 True

结论:文件是可迭代对象,也是迭代器

小结:

  • 凡是可作用于for循环的对象都是Iterable类型;
  • 凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
  • 集合数据类型如listdictstr等是Iterable但不是Iterator(只含有__iter__()不含__next__()),不过可以通过iter()函数获得一个Iterator对象。

Python3的for循环本质上就是通过不断调用next()函数实现的,例如:

 

1 for x in [1, 2, 3, 4, 5]:
2     pass

 

实际上完全等价于:

 1 # 首先获得Iterator对象:
 2 it = iter([1, 2, 3, 4, 5])
 3 # 循环:
 4 while True:
 5     try:
 6         # 获得下一个值:
 7         x = next(it)
 8     except StopIteration:
 9         # 遇到StopIteration就退出循环
10         break

  

对yield的总结

  (1)通常的for..in...循环中,in后面是一个数组,这个数组就是一个可迭代对象,类似的还有链表,字符串,文件。他可以是a = [1,2,3],也可以是a = [x*x for x in range(3)]。

它的缺点也很明显,就是所有数据都在内存里面,如果有海量的数据,将会非常耗内存。

  (2)生成器是可以迭代的,但是只可以读取它一次。因为用的时候才生成,比如a = (x*x for x in range(3))。!!!!注意这里是小括号而不是方括号。

  (3)生成器(generator)能够迭代的关键是他有next()方法,工作原理就是通过重复调用next()方法,直到捕获一个异常。

  (4)带有yield的函数不再是一个普通的函数,而是一个生成器generator,可用于迭代

  (5)yield是一个类似return 的关键字,迭代一次遇到yield的时候就返回yield后面或者右面的值。而且下一次迭代的时候,从上一次迭代遇到的yield后面的代码开始执行

  (6)yield就是return返回的一个值,并且记住这个返回的位置。下一次迭代就从这个位置开始。

  (7)带有yield的函数不仅仅是只用于for循环,而且可用于某个函数的参数,只要这个函数的参数也允许迭代参数。

  (8)send()和next()的区别就在于send可传递参数给yield表达式,这时候传递的参数就会作为yield表达式的值,而yield的参数是返回给调用者的值,也就是说send可以强行修改上一个yield表达式值。

  (9)send()和next()都有返回值,他们的返回值是当前迭代遇到的yield的时候,yield后面表达式的值,其实就是当前迭代yield后面的参数。

  (10)第一次调用时候必须先next()或send(),否则会报错,send后之所以为None是因为这时候没有上一个yield,所以也可以认为next()等同于send(None)

posted on 2019-03-20 03:05  Holy_Shit  阅读(221)  评论(0编辑  收藏  举报