什么是生成器
通过列表推导式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们可以在循环的过程中不断推算出后续的元素,这样就不必创建完整的 list,从而节省大量的空间。
通俗的理解:
在Python中,这种一边循环一边计算的机制,称为生成器:generator
只记录生成数据的方式(算法),而不事先生成且存储这些数据
生成器
其实利用迭代器也可以在每次迭代获取数据(通过next()方法)时按照特定的规律进行生成。
但在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合 next()函数进行迭代使用。
简而言之,迭代器是可以实现在循环的过程中生成数据的,但有点复杂。
一个具有迭代器功能,且比它更加简单的方式:生成器(generator)
生成器是一类特殊的迭代器
创建生成器的方法1
第一种方法:把一个列表生成式的 [ ] 改成 ( )
nums = [x for x in range(5)] # 列表
print(type(nums))
print(nums)
print('-' * 20)
nums2 = (x for x in range(5)) # 生成器
print(type(nums2))
print(nums2)
创建生成器的方法2
下面使用迭代器来实现一个不确定个数的斐波那契数列:
class FibIterator(object):
def __init__(self):
self.num1 = 1 # 前一个数,初始值为数列的第一个数
self.num2 = 1 # 后一个数,初始值为数列的第二个数
def __next__(self):
temp_num = self.num1
self.num1, self.num2 = self.num2, self.num1 + self.num2
return temp_num
def __iter__(self):
return self
fib = FibIterator()
print(next(fib)) # fib是迭代器,所以不必使用iter(),直接next()就行
print(next(fib))
print(next(fib))
下面使用生成器实现斐波那契数列:
def fib_generator():
num1 = 1
num2 = 1
while True:
temp_num = num1
num1, num2 = num2, num1 + num2
yield temp_num
fib = fib_generator()
print(fib) # <generator object fib_generator at 0x7f86844e57b0>
num = next(fib)
print(num)
在使用生成器实现的方式中,将原本在迭代器 next 方法中实现的基本逻辑放到一个函数中实现,但是将每次迭代返回数值的 return换成了 yield,此时新定义的函数便不再是函数,而是一个生成器了
即:只要在 def函数中有 yield关键字的 就称为生成器
此时按照调用函数的方式( fib = fib_generator() )就不再是执行函数体了,而是会返回一个生成器对象,然后就可以按照使用迭代器的方式来使用生成器了
yield关键字
1.只要有yield关键字,那么虽然看上去是调用函数,实际上已经变成了创建一个生成器对象
2.通过next调用生成器,可以让这个带有yield的def代码块,开始执行
i.如果是第一次执行,则从 def 代码块的开始部分执行,直到遇到 yield 为止,并且把 yield 关键字后的数值返回,当作 next() 的返回值
ii.如果不是第一次执行,则从上一次暂停的位置执行(即从上一次 yield 关键字的下一个语句开始执行),直到遇到下一次 yield 为止,并且把 yield 关键字后的数值返回,当作 next() 的返回值
获取return的值
def fib_generator():
num1 = 1
num2 = 1
temp_num = num1
num1, num2 = num2, num1 + num2
yield temp_num
temp_num = num1
num1, num2 = num2, num1 + num2
yield temp_num
temp_num = num1
num1, num2 = num2, num1 + num2
yield temp_num
return "end"
fib = fib_generator()
print(next(fib))
print(next(fib))
print(next(fib))
# 1.如果在调用next的时候,从上一次暂停的位置继续向下运行时,遇不到yield,就会产生异常
# 2.如果在调用next时,接下来没有遇到yield,而是遇到return,那也会产生异常,并且把return的数值用异常暂时存储起来
# print(next(fib))
try:
print(next(fib))
except StopIeration as ret:
print(ret.value) # 如果生成器中有return,那么python已经将它封装到了异常对象信息中的value属性
总结:调用 generator时,发现拿不到 generator 的 return语句的返回值。如果想要拿到返回值,必须捕获 StopIteration错误,返回值包含在 StopIteration 的 value中
使用 send 唤醒
除了可以使用 next() 函数来唤醒,让生成器继续执行外,还可以使用 send() 函数来唤醒执行。
使用 send() 函数的一个好处是:可以在唤醒的同时向断点处传入一个附加数据
相同点:
都会让生成器继续向下执行
如果运行时,遇不到yield,都会产生异常
不同点:
next只会让运行继续开始。而send除了可以让其开始运行之外,还可以将某个数据携带过去
def generator_test():
while True:
print("-----1")
num = yield 100
print("-----2", "num=", num)
g = generator_test()
print(next(g))
print(next(g))
print(next(g))
使用send:
def generator_test():
while True:
print("-----1")
num = yield 100
print("-----2", "num=", num)
g = generator_test()
print(next(g))
print(g.send(11))
print(g.send(22))
一个例子
需求:
1.不确定个数的 点坐标
2.第1次点坐标的x值是0,得到y值1;将此次的y当作第2次点的x坐标值
3.即 第2次点的坐标值是1,得到y值3;将此次的y当作第3次点的x坐标值
4.即 第3次点的坐标值是3,得到y值7;将此次的y当作第4次点的x坐标值
... 以此类推 ...
5.在不确定第几次时,可能需要修改方程(y=2x+1)组中的2与1的值
def create_point():
x = 0
k = 2
b = 1
while True:
# y = 2 * x + 1
y = k * x + b
temp = yield (x, y)
if temp:
k, b = temp
x = y
fib = create_point()
print(next(fib))
# print(next(fib))
print(fib.send((3, 2)))
print(next(fib))
创建多个生成器
注意:
如果一个def代码块中有yield,那么看上去调用这个def代码块实际上是创建一个新的生成器。
如果调用了多次这个def代码块,意味着创建了多个生成器,且每个生成器之间没有任何关系,各用各的