要讲生成器,不得不说的一个东西就是迭代器了,
所以先说一下迭代器。
迭代器
迭代,很简单,就是重复一个操作多次嘛。迭代器(Iterator)是一个对象,它的工作是遍历并选择序列中的对象,它提供了一种访问一个容器(container)对象中的各个元素,而又不必暴露该对象内部细节的方法。通过迭代器,开发人员不需要了解容器底层的结构,就可以实现对容器的遍历。由于创建迭代器的代价小,因此迭代器通常被称为轻量级的容器。
初识:
在python中,没有内置迭代器类型的对象,但是可以通过内置函数iter将str,tuple,list,dict,set等类型转换成一个迭代器对象。
>>>lst = [1,2,3]
>>>next(lst)
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: 'list' object is not an iterator
>>>next(iter(lst))
1
#
>>>lst_iter = iter(lst)
>>>next(lst_iter)
1
>>>next(lst_iter)
2
>>>next(lst_iter)
3
>>>next(lst_iter)
Traceback (most recent call last):
File "<input>", line 1, in <module>
StopIteration
当然了,通过不断的调用next(iterator)
方法来获取下一个值,这样其实很不方便的,python
提供了更为简洁的方法,即for
循环。for
循环每执行一次即相当于调用了一次next(iterator)
方法,直到捕获到StopIteration
异常退出循环。
>>>lst_iter = iter(lst)
>>>for i in lst_iter:
>>> print(i)
1
2
3
模块collections
中的类型Iterator
就是迭代器的抽象基类,所有的迭代器都是Iterator
的实例。即如果一个对象是Iterator
的实例,则说明此对象是迭代器。
from collections import Iterator
>>> isinstance(s,Iterator)
False
>>> isinstance(it_s,Iterator)
True
如何自己定义一个迭代器:
我们自定义的函数只要实现了
1.
__next()__
方法,该方法在每次被调用时不断返回下一个值,直到无法继续返回下一个值时抛出StopIteration
异常(next(iterator
)实际上调用的是iterator内部的__next()__
方法)。2.实现一个可迭代接口
Iterable,
迭代器类型Iterator
继承自可迭代类型Iterable
,可迭代Iterable
继承自object
基类,迭代器Iterator
类型包含__iter()__
和__next()__
方法,而可迭代类型Iteratble
仅仅包含__iter__()
。可迭代对象,通过__iter()__
返回一个迭代器对象,迭代器对象的__next()__
方法则实际用于被循环。
class MyIter():
def __init__(self,max_value):
self.current_value = 0
self.max_value = max_value
def __iter__(self):
return self
def __next__(self):
if self.current_value < self.max_value:
result = self.current_value
self.current_value += 1
return result
else:
raise StopIteration
读者们,可以自己去验证,是否可以用next方法返回下一个值,是否可以用for循环,是否属于itertor哦。
小结:
- 凡是可作用于
for
语句循环的对象都是Iterable
可迭代类型。- 凡是可作用于
next()
函数的对象都是Iterator
迭代器类型。str
、tuple
、list
、dict
、set
等类型是Iterable
可迭代类型,但不是Iterator
迭代器;通过Iterable
可迭代类型的__iter()__
方法可以获得一个Iterator
迭代器对象,从而使得它们可以被for语句循环。Python
的for
循环本质上就是通过调用Iterable
可迭代对象的__iter()__
方法获得一个Iterator
迭代器对象,然后不断调用Iterator
迭代器对象__next()__
方法实现的。
生成器
python中生成器提供了一种方便的方法来实现迭代器,而不需要必须实现__iter__()
和__next__()
两个迭代器方法。
生成器:一个生成器可以“生成”值。创建一个生成器其实就是创建一个函数而已,并没有太大的不同。
生成器的定义方式有两种,一种是调用生成器函数,一种是使用生成器表达式语法。
生成器函数是指在函数体中使用
yield
表达式仅返回结果的函数。
yield
表达式仅在定义生成器函数时使用,因此只能用在函数定义的主体中。在函数体中使用
yield
表达式会使该函数成为生成器函数。当生成器函数被调用时,它返回一个称为生成器的迭代器,该迭代器由python自动生成。然后,生成器控制了生成器函数的执行。因为返回的生成器是一个迭代器,所以生成器函数的执行结果也就可以被循环。当生成器的的
__next__
方法被调用时,生成器函数的函数体内的语句开始执行,执行进行到第一个yield
表达式时,立即将yield
表达式的结果返回给生成器的调用者,同时将生成器函数内部的状态挂起。即保持生成器函数的执行进度,和生成器函数内的局部状态:包括局部变量的当前绑定,指令指针,内部计算栈和任何异常处理的状态。当生成器的再次调用__next__
方法来时,生成器函数恢复执行,并再次执行到yield
表达式返回结果再保持状态,直到无法再执行到yield
表达式。此时生成器自动抛出StopIteration
异常。
1.调用生成器函数:
>>>def my_generator():
for i in range(10):
yield i,i*2
>>>my_generator()
<generator object my_generator at 0x0000024DAC0A4A20>
>>>for i in my_generator():
>>> print(i)
(0, 0)
(1, 2)
(2, 4)
(3, 6)
(4, 8)
(5, 10)
(6, 12)
(7, 14)
(8, 16)
(9, 18)
### 调用next方法
>>>g = my_generator()
>>>next(g)
(0, 0)
>>>next(g)
(1, 2)
>>>next(g)
(2, 4)
>>>next(g)
(3, 6)
>>>next(g)
(4, 8)
>>>next(g)
(5, 10)
>>>next(g)
(6, 12)
>>>next(g)
(7, 14)
>>>next(g)
(8, 16)
>>>next(g)
(9, 18)
>>>next(g)
Traceback (most recent call last):
File "<input>", line 1, in <module>
StopIteration
2.生成器表达式
除了使用生成器函数可以得到生成器,还可以生成器表达式得到生成器表达式。生成器表达式本身看起来像列表推导, 但不是用方括号而是用圆括号包围起来:
>>>g2 = (x**2 for x in range(10))
>>>g2
<generator object <genexpr> at 0x0000024DAC0A4930>
>>>t = (1,2,3,4,5)
>>>g3 = (x**2 for x in t)
>>>g3
<generator object <genexpr> at 0x0000024DAC0A4CF0>
和普通迭代器相比,生成器不单简化了迭代器的定义,还在使用效率上有提升。因为生成器在循环时,生成器函数每次只会返回一个结果,然后保持内部状态,所以生成器占用的内存是很小的。以下两个测试结果,第一个直接抛出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