1.迭代
在理解生成器之前,先理解迭代。
1.1 迭代
如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)
alist = [1, 2, 3, 4, 5]
for i in alist:
print(i)
1
2
3
4
5
正如将列表中的元素通过for循环,遍历了整个alist列表,这种不重复地便利其内部的每一个子项的行为就是迭代。
1.2 可迭代对象
可以直接作用于for循环的对象统称为可迭代对象:Iterable,可迭代对象一般都实现了__iter()__
方法,可迭代对象通过其内建的方__iter()__
返回一个迭代器对象。
a_iterable = [1, 2, 3]
a_iterator = iter(a_iterable) # 将可迭代对象转化为迭代器
next(a_iterator)
1
next(a_iterator)
2
next(a_iterator)
3
1.3 迭代器
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator,迭代器其内实现了__iter__
方法和__next__
方法,for循环本质是通过调用可迭代对象的__iter__
方法,该方法返回一个迭代器对象,再用__next__
方法遍历元素
定义一个迭代器:
class MyRange:
def __init__(self, end):
self.index = 0
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.index < self.end:
val = self.index
self.index += 1
return val
else:
raise StopIteration()
my_range = MyRange(3)
print([i for i in my_range])
[0, 1, 2]
print([i for i in my_range])
[]
迭代器只能迭代一次,每次调用调用 next() 方法就会向前一步,不能后退,所以当迭代器迭代到最后时,就不可以重复利用,所有需要将迭代器和可迭代对象分开定义
修改上面的可迭代对象:
class MyRange:
def __init__(self, end):
self.end = end
def __iter__(self):
return MyIterator(self.end)
class MyIterator:
def __init__(self, end):
self.index = 0
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.index < self.end:
val = self.index
self.index += 1
return val
else:
raise StopIteration()
my_range = MyRange(3)
print([i for i in my_range])
[0, 1, 2]
print([i for i in my_range])
[0, 1, 2]
2. 生成器
生成器与可迭代对象、迭代器的关系
图片来自Iterables vs. Iterators vs. Generators
生成器对象,在每次调用它的next()方法时返回一个值,直到它抛出StopInteration。
生成器是可以迭代的,但是你 只可以读取它一次 ,因为它并不把所有的值放在内存中,它是实时地生成数据, 可以用生成器表达式创建:
my_generator = (x ** 2 for x in range(3))
my_generator
<generator object <genexpr> at 0x7f975b7a4af0>
for i in my_generator:
print(i)
0
1
4
yield
可以写一个普通的包含yield语句的Python函数,Python会检测对yield的使用并将函数标记为一个生成器,当函数执行到yield语句时,像return语句那样返回一个值,但是解释器会保存对栈的引用,它会被用来在下一次调用next时恢复函数。
def my_generator():
yield 1
yield 2
yield 'a'
yield 'generator'
g = my_generator()
g
<generator object my_generator at 0x7f975b7a4d58>
next(g)
1
next(g)
2
next(g)
'a'
next(g)
'generator'
next(g)
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-12-5f315c5de15b> in <module>()
----> 1 next(g)
StopIteration:
上面的例子中,每次调用next()开始实时地生成数据,并返回,因此生成器只可读取一次,上次执行读取的值在下次执行中就无法读取。当整个生成器的值都被读取后,在调用机会出现StopIteration的错误。
def my_gen():
for i in range(5):
yield i ** 3
my_gen()
<generator object my_gen at 0x7f975ae15a40>
mygen = my_gen()
for i in mygen:
print(i)
0
1
8
27
64
每次执行到yield语句,则返回一个值,再执行的时候从上次停下来的地方开始执行。yield语句保存了上次执行后的状态,下次执行不是从头开始,而是从上次的状态开始。
当调用my_gen()这个函数的时候,函数内部的代码不会立即执行,而是返回一个生成器对象,当利用for循环进行遍历的时候,函数内部的代码开始执行,执行到yield表达式返回一个值,记录当前状态并停下,下一次的访问时再从这个状态开始执行。
举一个不太恰当的例子,普通的函数就是没有存档的游戏,只要游戏开始,就玩到结尾,下一次再玩还是从头开始,而生成器就是加了存档,下次玩从上次存档的地方开始
关于生成器的思考
(瞎掰的。。。。)生成器到底起到什么吗作用呢,就算生成一个生成器对象,而生成器对象一定是一个迭代器,所以可以这么说,生成器返回了一个可以用for循环遍历所以子项,可以用next()方法访问下一个子项,可以在访问时动态的生成数据而节省内存的对象。
阅读
完全理解 Python 迭代对象、迭代器、生成器
对 Python 迭代的深入研究
Python迭代器和生成器
3. (译)Python关键字yield的解释(stackoverflow)
Python之列表生成式、生成器、可迭代对象与迭代器