什么是生成器 - generator
生成器算得上是Python语言中最吸引人的特性之一,生成器其实是一种特殊的迭代器,
不过这种迭代器更加优雅。
它不需要手动编写__iter__()和__next__()方法,只需要一个yiled关键字。
• 生成器一定是迭代器(反之不成立)。
• 因此任何生成器也是以一种懒加载的模式生成值。
生成器表达式
• 使用 ( ) 生成 generator, 将俩表推导式的 [ ] 改成 ( ) 即可得到生成器。
• 列表推导式与生成器表达式,定义和使用非常类似。但是,在(大量数据处理时)性能上相
差很大。
a = (x for x in range(10**9))
print(a)
print(dir(a))
print(next(a))
print(next(a))
print(next(a))
# <generator object <genexpr> at 0x000001E405F16C80>
# ['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
# 0
# 1
# 2
yield关键字
包含yield表达式的函数是特殊的函数,叫做生成器函数(generator function),被调用时将返回一个
迭代器(iterator),调用时可以使用next或send(msg)。
• 一个生成器中可以有多个yield。
• 一旦遇到yield,就会保存当前状态,然后返回yield后面的值。
• 当生成器遇到一个yield时,会暂停运行生成器,返回yield后面的值。
• 当再次调用生成器的时候,会从刚才暂停的地方继续运行,直到下一个yield。
• yield关键字:保留中间算法,下次继续执行。
def ger_content():
x = 8
yield x-1
y = 6
yield y+2
z = 3
yield z
g = ger_content()
print(type(g))
print(next(g)) # 第一次获取数据完了后会退出函数,保留上次执行的位置
print(next(g)) # 第二次再次执行的时候会从上一次执行的位置再次执行
print(next(g))
# <class 'generator'>
# 7
# 8
# 3
yield生成器来实现斐波那契数列
from itertools import islice
def fib():
prev, curr = 0, 1
while True:
yield curr
prev, curr = curr, curr + prev
f = fib()
print(list(islice(f, 0, 10)))
# [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
生成器 fib() 的执行过程:
• 当执行f=fib()返回的是一个生成器对象,此时函数体中的代码并不会执行,而是首先返回一个
iterable 对象!
• 只有显示或隐示地调用next的时候才会真正执行函数里面的代码。
• 执行到语句 yield b 时,fab() 函数会返回yield后面(右边)的值,并记住当前执行的状态。
• 下次调用next时,程序流会回到 yield b 的下一条语句继续执行。
• 看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返
回当前的迭代值。
• 由此可以看出,生成器通过关键字 yield 不断的将迭代器返回到内存进行处理,而不会一次性
的将对象全部放入内存,从而节省内存空间。
send数据
除了可以使用 next() 方法来获取下一个生成的值,用户还可以使用 send() 方法将一个
新的或者是被修改的值返回给生成器。除此之外,还可以使用 close() 方法来随时退出
生成器。
def counter(start_at=0):
count = start_at
while True:
val = (yield count) # send 发送数据赋值给val,next获取时,val值为None
if val is not None:
count = val
else:
count += 1
count = counter(5)
print(type(count))
# generator
print(count.__next__())
print(count.send(9)) # 返回一个新的值给生成器中的yield count
print(count.__next__())
count.close() # 关闭一个生成器
print(count.__next__())
# <class 'generator'>
# 5
# 9
# 10
yield from
def g1(x):
yield range(x)
def g2(x):
yield from range(x)
it1 = g1(5)
it2 = g2(5)
print( [ x for x in it1] )
#out [range(0, 5)]
print( [ x for x in it2] )
#out [0, 1, 2, 3, 4]
生成器的应用
当你需要以迭代的方式去处理一个巨大的数据集合。比如:一个巨大的文件/一个复杂的数据库查
询等。
如果直接对文件对象调用 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
生成器的优点
生成器在Python中是一个非常强大的编程结构:
• 可以用更少地中间变量写流式代码。
• 相比其它容器对象它更能节省内存。
• 可以用更少的代码来实现相似的功能。
重写代码
凡看到类似:
def something():
result = []
for ... in ...:
result.append(x)
return result
都可以根据实际需求用生成器函数来替换:
def iter_something():
for ... in ...:
yield x
可迭代对象、迭代器、生成器
可迭代对象:实现了__iter__, 返回了一个迭代器对象。
迭代器:Python中一个实现了_iter_方法和_next_方法的类对象,就是迭代器
生成器:延迟操作。也就是在需要的时候才产生结果,不是立即产生结果。
• 生成器是只能遍历一次的。
• 生成器是一类特殊的迭代器。
三种关系小结
• 对象是可迭代对象, 但却不一定是迭代器。
• 如果对象属于迭代器, 那么这个对象一定是可迭代的。
• 迭代器实现了一个__next__()方法,可以通过next函数每次取出迭代器中的每个元素。
• 迭代器对象:__iter__()返回的是迭代器对象自身。
• 生成器是一种特殊的迭代器。