您的位置:首页 > 编程语言 > Python开发

Python内置类型(6)——生成器

2017-12-21 13:53 363 查看

上节内容说到Python的
for
语句循环本质上就是通过调用
Iterable
可迭代对象的
__iter()__
方法获得一个
Iterator
迭代器对象,然后不断调用
Iterator
迭代器对象
__next()__
方法实现的。
Iterator
迭代器对象则是一个需要实现
__iter__()
__next__()
两个迭代器协议方法的对象。python中生成器提供了一种方便的方法来实现迭代器协议,而不需要必须实现
__iter__()
__next__()
两个迭代器协议方法。


生成器的定义方式有两种,一种是调用生成器函数,一种是使用生成器表达式语法。

调用生成器函数

生成器函数是指在函数体中使用
yield
表达式仅返回结果的函数。
yield
表达式仅在定义生成器函数时使用,因此只能用在函数定义的主体中。在函数体中使用
yield
表达式会使该函数成为生成器函数。当生成器函数被调用时,它返回一个称为生成器的迭代器,该迭代器由python自动生成。然后,生成器控制了生成器函数的执行。因为返回的生成器是一个迭代器,所以生成器函数的执行结果也就可以被循环。当生成器的的
__next__
方法被调用时,生成器函数的函数体内的语句开始执行,执行进行到第一个
yield
表达式时,立即将
yield
表达式的结果返回给生成器的调用者,同时将生成器函数内部的状态挂起。即保持生成器函数的执行进度,和生成器函数内的局部状态:包括局部变量的当前绑定,指令指针,内部计算栈和任何异常处理的状态。当生成器的再次调用
__next__
方法来时,生成器函数恢复执行,并再次执行到
yield
表达式返回结果再保持状态,直到无法再执行到
yield
表达式。此时生成器自动抛出
StopIteration
异常。

我们先定义一个简单生成器函数,函数功能返回数字
0-9
的平方数

# 生成器函数,功能返回数字0-9的平方数
>>> def squares():
for i in range(10):
yield i**2

# 使用return关键字是普通函数,使用yield关键字函数变成了生成器函数

使用参数
g
接收调用生成器函数
squares
的结果,然后分别在shell查看
squares
g
这两个变量的类型

>>> g = squares()

#查看squares对象类型
>>> squares
<function squares at 0x035950C0>

#查看g对象类型
>>> g
<generator object squares at 0x0358A930>

从上面可以看出变量
squares
是函数类型,变量
g
generator
类型对象,
generator
从字面的理解上就是生成器类型。根据上一节迭代器中提到的知识,从
collection
模块引入
Iterator
的抽象基类,验证下
generator
类型是不是上面说的迭代器类型。

>>> from collections import Iterator
>>> isinstance(g,Iterator)
True

验证成功,说明生成器函数的执行结果确实是生成器,一种特殊的迭代器。

>>> for i in g:
print (i)

0
1
4
9
16
25
36
49
64
81

生成器表达式

除了使用生成器函数可以得到生成器,还可以生成器表达式得到生成器表达式。生成器表达式本身看起来像列表推到, 但不是用方括号而是用圆括号包围起来:

>>> g2 = (x**2 for x in range(10))
>>> g2
<generator object <genexpr> at 0x0359AFC0>

>>> t = (1,2,3,4,5)
>>> g3 = (x**2 for x in t)
>>> g3
<generator object <genexpr> at 0x007F6180>

验证:

>>> isinstance(g2,Iterator)
True

>>> isinstance(g3,Iterator)
True

使用:

>>> for i in g2:
print(i)

0
1
4
9
16
25
36
49
64
81

>>> for i in g3:
print(i)

1
4
9
16
25

和普通迭代器相比,生成器不单简化了迭代器的定义,还在使用效率上有提升。因为生成器在循环时,生成器函数每次只会返回一个结果,然后保持内部状态,所以生成器占用的内存是很小的。以下两个测试结果,第一个直接抛出
MemoryError
异常,第二个只能正确计算出结果。

# 全部数据先加载在1个列表上面,内存占用高
>>> s1 = sum([i for i in range(100000000)])
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
s1 = sum([i for i in range(100000000)])
File "<pyshell#6>", line 1, in <listcomp>
s1 = sum([i for i in range(100000000)])
MemoryError

# 数据几乎不占内存
>>> s2 = sum((i for i in range(100000000)))
>>> s2
4999999950000000
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: