生成器
[TOC]
第三章 生成器
3.1 理解生成器
生成器处理值序列时允许序列中的每一个值只在需要时才计算
使用生成器可以节省大量内存
生成器能够处理一些无法由列表准确表示的序列形式
生成器是一个函数, 按照顺序返回一个或多个值
3.2 生成器语法
通常, 生成器在函数内部包含一个或多个
yield
语句像
return
语句一样,yield
语句命令函数返回一个值给调用者, 但不会终止函数的执行, 执行会暂停直到调用代码重新恢复生成器, 在停止的地方再次开始执行简单示例
# 代码 def fibonacci(): yield 1 yield 1 yield 2 yield 3 yield 5 yield 8 pass for i in fibonacci(): print(i) pass
# 输出 D:\Software\Installed\Python\Python36\python.exe D:/workspace/Pycharm/PurePythonProject/Test01.py 1 1 2 3 5 8 Process finished with exit code 0
3.2.1 next
函数
next
函数Python
内置了next
函数, 能够让生成器请求它的下一个值简单示例
# 代码 def fibonacci(): numbers = [] while True: if len(numbers) < 2: numbers.append(1) pass else: numbers.append(sum(numbers)) pass yield numbers[-1] pass gen = fibonacci() for i in range(1, 10): print(next(gen))
# 输出 D:\Software\Installed\Python\Python36\python.exe D:/workspace/Pycharm/PurePythonProject/Test02.py 1 1 2 4 8 16 32 64 128 Process finished with exit code 0
3.2.2 终止生成器: StopIteration
异常
StopIteration
异常Python 2
中, 函数内不允许yield
和return
同时存在, 可以通过抛出StopIteration
异常来终止生成器简单示例
import sys print("Python版本信息: ", sys.version)
def generator(): yield 1 yield 2 raise StopIteration yield 3 pass
print([i for i in generator()])
gen = generator() while True: print(next(gen))
**输出**
```python
D:\Software\Installed\Python\Python36\python.exe D:/workspace/Pycharm/PurePythonProject/Test03.py
Traceback (most recent call last):
Python版本信息: 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)]
File "D:/workspace/Pycharm/PurePythonProject/Test03.py", line 17, in <module>
[1, 2]
print(next(gen))
1
File "D:/workspace/Pycharm/PurePythonProject/Test03.py", line 9, in generator
2
raise StopIteration
StopIteration
Process finished with exit code 1
从上例: 如果迭代这个生成器, 会得到1和2, 并可以正常退出(不抛出
StopIterator
异常); 如果使用next
获取生成器的下一个值, 则会抛出StopIterator
异常Python 3
中, 可以通过return
语句终止生成器. 但是,return
返回的值不会成为最终输出的值, 相反, 这个值会被作为异常信息发送.return
语句实际等同于raise StopIteration
简单示例
import sys print("Python版本信息: ", sys.version)
def generator(): yield 1 yield 2 return 4 yield 3 pass
print([i for i in generator()])
gen = generator() while True: print(next(gen))
**输出**
```shell
D:\Software\Installed\Python\Python36\python.exe D:/workspace/Pycharm/PurePythonProject/Test04.py
Python版本信息: 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)]
[1, 2]
1
2
Traceback (most recent call last):
File "D:/workspace/Pycharm/PurePythonProject/Test04.py", line 17, in <module>
print(next(gen))
StopIteration: 4
Process finished with exit code 1
3.3 与生成器之间的交互
生成器协议提供了
send
方法, 以允许与生成器的反向沟通
简单示例
import sys
print("Python版本信息: ", sys.version)
def squares():
a = 1
b = 1
while True:
print('A: ', a, ' ', b)
response = yield a + b
print('B: ', a, ' ', b)
if response:
b = int(response)
else:
b += 1
print('C: ', a, ' ', b)
pass
sq = squares()
print(next(sq))
print(next(sq))
print(sq.send(7))
print(next(sq))
输出
D:\Software\Installed\Python\Python36\python.exe D:/workspace/Pycharm/PurePythonProject/Test04.py
Python版本信息: 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)]
A: 1 1
2
B: 1 1
C: 1 2
A: 1 2
3
B: 1 2
C: 1 7
A: 1 7
8
B: 1 7
C: 1 8
A: 1 8
9
Process finished with exit code 0
说明
send
函数向生成器传递一个参数, 该参数为yield
表达式的值yield
语句执行后暂停, 在下次调用时才会继续运行, 直到遇到下一条yield
语句
3.4 迭代器对象与迭代器
Python中, 生成器是一种迭代器
Python中的迭代器是包含
__next__
方法的任何对象迭代对象 是任何定义了
__iter__
方法的对象, 可迭代对象的__iter__
方法负责返回一个迭代器生成器可以是迭代器, 但不一定是迭代对象, 相似的, 并非所有的迭代对象都是迭代器
以range
为例
range
为例range
对象不是生成器, 因为range
对象没有定义__next__
函数, 不能作为next()
函数的参数但是,
range
对象的__iter__
方法返回的实际迭代器是一个生成器, 可以响应next
方法. 但是不能响应send
方法D:\workspace\Pycharm\PurePythonProject>python Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> r = range(0, 5) >>> r range(0, 5) >>> next(r) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'range' object is not an iterator >>> type(r) <class 'range'> >>> it = iter(r) >>> it <range_iterator object at 0x0000014A70D9AEF0> >>> next(it) 0 >>> next(it) 1 >>> it.send(2) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'range_iterator' object has no attribute 'send'
语句
for i in range(0, 5)
之所以可以运行, 是因为range
函数返回了一个迭代对象注意: 并非所有的迭代器都是
generator
类的实例.range
迭代器是range_iterator
的实例, 实现了相似的模式, 且缺少send
方法 的实现
3.5 标准库中的生成器
3.5.1 range
range
在
Python 2
中成为xrange
range
对象的迭代器(由__iter__
返回)是个生成器(range_iterator
)
3.5.2 dict.items
家族
dict.items
家族在
Python
中, 内置的字典类包括3个允许迭代所有字典的方法, 并且这3个方法都是迭代器是生成器的迭代对象(keys
,values
,items
, 注意:在Python 2
中, 三个方法分别是:iterkeys
,itervalues
,iteritems
)把
dict.items
声明为生成器, 就不需要将整个字典重新格式化为包含键值的二元组列表简单示例
D:\workspace\Pycharm\PurePythonProject>python Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> d = {'foo': 'bar', 'baz': 'bacon'} >>> it = iter(d.items()) >>> next(it) ('foo', 'bar') >>> next(it) ('baz', 'bacon') >>> next(it) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration >>>
生成器返回所有数据后, 继续作为
next
函数的参数时, 会抛出StopIteraion
异常在迭代期间修改字典, 可能会看到副作用
D:\workspace\Pycharm\PurePythonProject>python Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> d = {'foo': 'bar', 'baz': 'bacon'} >>> it = iter(d.items()) >>> next(it) ('foo', 'bar') >>> d['spam'] = 'eggs' >>> next(it) Traceback (most recent call last): File "<stdin>", line 1, in <module> RuntimeError: dictionary changed size during iteration
3.5.3 zip
zip
Python
包含了一个名为zip
的内置函数, 该函数有多个可迭代对象, 并且一起迭代所有对象D:\workspace\Pycharm\PurePythonProject>python Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> z = zip([1, 2, 3, 4], ['a', 'b', 'c'], ['A', 'B']) >>> next(z) (1, 'a', 'A') >>> next(z) (2, 'b', 'B') >>> next(z) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
到达最短的迭代对象的最后一个元素时停止
3.5.4 map
map
map
函数的参数为: 一个接受N个参数的函数, 以及N个迭代对象, 并且计算每个迭代对象的序列成员的函数结果, 到达最短的迭代对象的最后一个元素时停止D:\workspace\Pycharm\PurePythonProject>python Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> m = map(lambda x, y: x + y, [1, 2, 3], [4, 5]) >>> next(m) 5 >>> next(m) 7 >>> next(m) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
3.5.5 文件对象
可以利用生成器逐行读取文本文件内容
D:\workspace\Pycharm\PurePythonProject>cat lines.txt line 1 line 2 line 3 line 4 line 5 D:\workspace\Pycharm\PurePythonProject>python Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> f = open('lines.txt') >>> next(f) 'line 1\n' >>> f.readline() 'line 2\n' >>> next(f) 'line 3\n' >>> f.readline() 'line 4\n' >>> next(f) 'line 5\n' >>> next(f) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration >>> f.readline() ''
由上面代码可以看出,
next(f)
和f.readline()
语句的返回结果来自同一生成器对象, 但是,__next__
函数(使用next(f)
时会调用)和readline
函数不完全一样,readline
函数会捕捉StopIteration
异常, 并返回一个空字符串
3.6 生成器单例模式
简单的生成器函数并不是单例模式的, 多次调用函数将返回不同的生成器实例
import sys print("Python版本信息: ", sys.version)
def fibonacci(): numbers = [] while True: if len(numbers) < 2: numbers.append(1) pass else: numbers.append(sum(numbers)) pass yield numbers[-1] pass
gen1 = fibonacci() print(next(gen1), next(gen1), next(gen1)) gen2 = fibonacci() print(next(gen2), next(gen2), next(gen2)) print(next(gen1), next(gen1), next(gen1))
**输出**
```shell
D:\Software\Installed\Python\Python36\python.exe D:/workspace/Pycharm/PurePythonProject/Test05.py
Python版本信息: 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)]
1 1 2
1 1 2
4 8 16
Process finished with exit code 0
下面的可迭代类可以实现类似单例模式的目的
import sys print("Python版本信息: ", sys.version) class Fibonacci(object): def init(self): self.numbers = [] pass def __iter__(self): return self def __next__(self): if len(self.numbers) < 2: self.numbers.append(1) pass else: self.numbers.append(sum(self.numbers)) self.numbers.pop(0) pass return self.numbers[-1] def send(self, value): pass # 兼容 Python 2 next = __next__ pass f = Fibonacci() i1 = iter(f) print(next(i1), next(i1), next(i1)) i2 = iter(f) print(next(i2))
输出
D:\Software\Installed\Python\Python36\python.exe D:/workspace/Pycharm/PurePythonProject/Test05.py
Python版本信息: 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)]
1 1 2
3
Process finished with exit code 0
3.7 生成器内部的生成器
一个生成器调用另外一个生成器是可行的
Python 3.3
引入了新的yield from
语句, 旨在为生成器提供一种调用其他生成器的直接方式简单示例
import sys import itertools print("Python版本信息: ", sys.version) def gen1(): yield 'foo' yield 'bar' pass def gen2(): yield 'spam' yield 'eggs' pass
第一种调用方式
def full_gen_1(): for word in gen1(): yield word pass for word in gen2(): yield word pass pass
第二种调用方式, 使用 itertools.chain
def full_gen_2(): for word in itertools.chain(gen1(), gen2()): yield word pass pass
第三种调用方式, 使用 yield from 语句
def full_gen_3(): yield from gen1() yield from gen2() pass
print([i for i in full_gen_1()]) print([i for i in full_gen_2()]) print([i for i in full_gen_3()])
**输出**
```shell
D:\Software\Installed\Python\Python36\python.exe D:/workspace/Pycharm/PurePythonProject/Test06.py
Python版本信息: 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)]
['foo', 'bar', 'spam', 'eggs']
['foo', 'bar', 'spam', 'eggs']
['foo', 'bar', 'spam', 'eggs']
Process finished with exit code 0
yield from
被称为生成器委托使用
yield from
时, 该生成器委托给另一个生成器, 任何使用send
函数发送个生成器的值, 都将被发送给当前委托生成器. 而前两种实现, 则需要手动传递该值.
最后更新于
这有帮助吗?