因为生成器的其中一种建立方式与列表推导式很类似,这里先说一下列表推导式。html
列表推导式又叫列表生成式,官方叫作 list comprehension。顾名思义,这个是用来生成列表的。python
用法:express
[x for x in iterable]
通常状况下,能够用list()函数将序列转换成列表,好比:app
>>> list(range(1, 11)) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
上面的状况比较单一,若是复杂一点,好比要生成[1x1, 2x2, 3x3, ..., 10x10]
,这个时候就只能循环了,列表推导式就是用来简化这种状况的。函数
>>> [x * x for x in range(1, 11)] [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
把要生成的元素x * x
放在前面,后面跟for循环,就能够把列表建立出来。运用列表推导式,能够写出很是简洁的代码。code
>>> L = ['Hello', 'World', 'IBM', 'Apple'] >>> [s.lower() for s in L] ['hello', 'world', 'ibm', 'apple']
经过列表推导式,能够直接生成一个列表,可是内存是有限的,若是一个列表太大,或者只须要访问列表中的部分元素,那其他大部分元素占用的空间就浪费了,生成器就是用来解决这个问题的。htm
生成器在访问序列中的元素时,不是一下加载整个序列到内存,而是一边循环一边计算,也就是读取一个元素计算一次,这样若是序列很大就节省了大量空间。说白了,生成器就是一种特殊的迭代器。对象
生成器有两种建立方法,第一种与列表推导式相似,只须要把列表推导式中的方括号[]
改为圆括号()
便可建立一个生成器(generator)。内存
>>> L = [x * x for x in range(10)] >>> L [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>> g = (x * x for x in range(10)) >>> g <generator object <genexpr> at 0x0000000001E74CA8> >>> for i in g: ... print(i) ... 0 1 4 9 16 25 36 49 64 81
建立生成器的第二种方法,是经过生成器函数。所谓生成器函数,就是在一个函数中包含yield
语句。get
好比斐波那契函数:
>>> def fib(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1 return 'done' ... ... ... ... ... ... ...
带有 yield 的函数再也不是一个普通函数,而是一个generator。
>>> fib(10) <generator object fib at 0x0000000001DE6A40> >>> list(fib(10)) [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
须要注意的是,生成器函数和普通函数执行的流程是不同的。普通函数是遇到 return 语句或者执行结束就返回。而生成器函数是遇到 yield 就返回一个迭代值,再次执行时从上次返回的 yield 语句处继续执行,看起来就像一个函数在正常执行的过程当中被 yield 中断了数次,每次中断都会经过 yield 返回当前迭代值。
好像说的有点绕,简单来讲,yield就是中断函数并返回迭代值,并且中断不会对函数变量形成影响即状态不变,返回迭代值以后继续执行函数。
再看看下面这个示例:
>>> def odd(): print('step 1') yield 1 print('step 2') yield(3) print('step 3') yield(5) >>> odd() <generator object odd at 0x0000000001DE6A98> >>> for i in odd(): ... print(i) ... step 1 1 step 2 3 step 3 5
遇到yield就返回迭代值,这个返回相似return但不会退出函数(能够理解为挂起),而后继续执行函数,直到再次遇到yield。
另外,上面生成器函数fib()最后的retrun的值没有返回(在普通函数中是能够返回的)。由于,调用生成器函数并无执行函数代码,而是返回了一个generator object
,而后再从generator object
中迭代取值的时候才会真正执行函数代码,因此调用生成器函数的时候并无报异常并退出,return的值也没有返回。
在生成器中,不管迭代有没有完成,遇到return语句都会直接终止迭代并抛出StopIteration异常。。
这里顺便说一下迭代器,迭代器能够经过内置函数next()获取迭代器中的下一个元素,迭代完成时会报StopIteration异常。
>>> g = (x * x for x in range(5)) >>> next(g) 0 >>> next(g) 1 >>> next(g) 4 >>> next(g) 9 >>> next(g) 16 >>> next(g) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
在 for 循环里,无需处理 StopIteration 异常,循环会正常结束。可是在while循环中会抛出异常,这时,须要捕获异常才会继续执行return语句,return的返回值包含在StopIteration的value中。
>>> g=fib(6) >>> while True: x = next(g) print('g:', x) ... ... ... g: 1 g: 1 g: 2 g: 3 g: 5 g: 8 Traceback (most recent call last): File "<stdin>", line 2, in <module> StopIteration: done
再来看上面的fib()函数的StopIteration异常捕获:
>>> g = fib(6) >>> 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 Generator return value: done
另外一个示例,文件读取:
若是直接对文件对象调用 read() 方法,会致使不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。
若是读取的文件很大,yield会分批返回,有效地避免了内存占用问题,轻松实现文件读取。
>>> def read_file(fpath): BLOCK_SIZE = 1024 with open(fpath, 'rb') as f: while True: block = f.read(BLOCK_SIZE) if block: yield block else: return >>> g = read_file('users.txt') >>> for i in g: ... print(i) ... b'keith1:18:110\r\nkeith2:19:111\r\nkeith3:20:112\r\nkeith4:21:113'
参考:
https://docs.python.org/3/library/stdtypes.html#lists
https://docs.python.org/3/reference/simple_stmts.html#the-yield-statement
https://docs.python.org/3/reference/expressions.html#yieldexpr