生成器详解——简单生成器
- 一、生成器(Generator)
- (一)设计目的
- (二)定义
- 1.```def generate_even(max): ```
- 2.```even_generator```
- 3.```even_generator.__next__()```
- (三)特别的 yield
- (四)总结:生成器的4个状态
- 二、普通函数模拟生成器函数
- 示例1
- 示例2
- 三、应用场景
- 生成器的三种应用场景
- 示例1
- 示例2
- 四、参考资料
一、生成器(Generator)
(一)设计目的
请读者先自行阅读下面这段代码。
# 目的:求一个数的平方
my_list = [x * x for x in range(5)]
for v in my_list:
print(v,end = " ")
print("\n")
#--------------------------------------------
# 0 1 4 9 16
我们很容易就能发现,上面这段代码其实是一个动态列表。
此时我们其实只要求产生 5 个数字存放在 my_list 中,这个时候,数据的存放尚且没有什么问题。但如果是 5亿个呢? 那么,这个列表占用的字节数就难以想象了,也就是造成内存空间的浪费。
为此,我们就有了 生成器!
(其实生成器还有更深层次的目的,就是
为了实现多线程的任务,
但此处只介绍简单生成器。)
(二)定义
生成器:一个使用了 yield 的函数,每次调用都能生成一个值。
虽然各位读者已经看到上面的定义,但关于生成器还有几个概念性的知识,笔者想先请各位读者阅读以下这段代码。
# 目的:打印不大于10的偶数
def generate_even(max):
for i in range(max+1):
if i % 2 == 0:
yield i
print(generate_even(10))
even_generator = generate_even(10)
print(even_generator.__next__()) # 0
print(even_generator.__next__()) # 2
#--------------------------------------------
# <generator object generate_even at 0x000001D842E7D048>
# 0
# 2
以上就是一个生成器的创建及调用。
1.def generate_even(max):
这首先是一个函数,当这个函数出现yield
的时候,函数就会产生变异,变成一个生成器函数,哪怕这个函数还没执行。
2.even_generator
根据打印结果:generator object ,我们知道even_generator
其实是一个生成器对象,也就是说:调用生成器函数会生成:生成器对象。
即:调用generate_even(10)
时,会返回生成器对象保存到even_generator
里。
注意:此时的函数其实还没有真正开始执行!
3.even_generator.__next__()
当even_generator 其实调用了 __next__
方法,才开始真正运行函数。
此处值得注意的是:函数用了 for
循环但却没有将所有不大于10的偶数打印出来,这就很奇怪了不是吗?
之所以会产生这样类似于 “暂停” 的效果,是因为 yield 在捣蛋。
(三)特别的 yield
1. yield只能放在函数内
2. 在函数内任何地方出现了 yield 关键字,哪怕永远无法被执行到,函数都会发生变异。
yield关键字最根本的作用是改变了函数的性质:
(1)调用生成器函数不是直接执行其中的代码,而是返回一个对象。
(2)生成器函数内的代码,需要通过生成器对象来执行。
示例
def gen():
print("Hello!") # 不执行
if 1:
yield
g = gen()
print(g)
# 证明1:
print(type(gen))
print(type(g))
#-----------------------------------------------------------
# <generator object gen at 0x000001908A0CF0C8>
# 证明1:
# <class 'function'>
# <class 'generator'>
# 更细致的证明2:
import inspect
# gen 是函数 也是生成器函数
print(inspect.isfunction(gen))
print(inspect.isgeneratorfunction(gen))
#-----------------------------------------------------------
# True
# True
# 生成器函数不是 generator,
# 生成器对象才是 generator,全称是 generator iterator 生成器迭代器(说明其本质是一个迭代器)
3. 当使用了 __next__
方法之后,生成器才真的能被执行
生成器对象就是迭代器,所以它的运行方式和迭代器是一致的:
(1)通过next()函数来调用
(2)每次next()都会在遇到yield后返回结果(作为next()的返回值),next() 由 yield 来控制
(3)如果函数运行结束(即遇到return)则抛出 StopIteration异常
示例1
def gen_666(meet_yield):
print("Hello!")
if meet_yield:
print("yield!")
yield 666 # 在这里暂停住,等再次调用(没有完成一次迭代),才向下走!
print("Back!")
print("Bye!")
return 'result' # 就是用来 触发异常
g2 = gen_666(True)
print(next(g2)) # 第一次
print(next(g2)) # 第二次
#-------------------------第一次------------------------
Hello!
yield!
666
#-------------------------第二次------------------------
Back!
Bye!
示例2
# 目的:实时生成 一个数的平方
data_generator = (x*x for x in range(5)) # 生成器表达式
print(type(data_generator))
for v in data_generator:
print(v,end = " ")
print()
for v in data_generator:
print(v,end = " ") # None -- 空
#--------------------------------------------
# <class 'generator'>
# 0 1 4 9 16
# (没有东西了) --> None
4. 在循环中使用yield
只遭遇一次yield语句的生成器就是只能迭代一次的迭代器,通常没什么实用价值。要想能迭代多次,可以在函数内多次使用yield语句:
示例1
def gen_func():
print('--- yeild 1 ---')
yield 1
print('--- yeild 2 ---')
yield 2
print('--- yeild 3 ---')
yield 3
一般是放在循环里来使用
示例2
def count(start = 0,step = 1):
# count(10) --> 10 11 12 13 14 ...
# count(2.5,0.5) --> 2.5 3.0 3.5 ...
n = start
while True:
yield n
n += step
if n == 6.5:
break
c = count(2.5,1)
# first = next(c) # 没必要写,for loop 就会调用 __iter__ and __next__ 方法。
# 写了之后会把第一个值给 firts ,然后不能将之打印出来
for i in c:
print(i)
#-----------------------------------------------------------
2.5
3.5
4.5
5.5
(四)总结:生成器的4个状态
1.当调用生成器函数得到生成器对象时
此时的生成器对象可以理解为处于初始状态(没有任何代码开始执行)
2.通过next()调用生成器对象,对应的生成器函数代码开始运行
此时生成器对象处于运行中状态
3.如果遇到yield语句,next()返回
(1) yield语句右边的对象作为 next()的返回值
(2) 生成器在yield语句所在的位置 暂停 ,当再次使用next()时继续从该位置 继续运行
4.如果执行到函数结束,则抛出 StopIteration异常
(1) 不管是使用了return语句显式地返回值,或者默认返回None值,返回值都只能作为异常的值一并抛出
(2) 此时的生成器对象处于结束的状态
(3) 对于已经结束的生成器对象再次调用next(),直接抛出StopIteration异常,并且不含返回值
二、普通函数模拟生成器函数
示例1
# 目的:打印不大于10的偶数
def even(max):
result = []
for i in range(max+1):
if i % 2 == 0:
result.append(i)
return result
for v in even(10):
print(v,end = " ")
#--------------------------------------------
# 0 2 4 6 8 10
示例2
# 目的:倒数计时器
class DownCounter:
def __init__(self,start):
self.start = start
def __iter__(self):
return self
def __next__(self):
if self.start > 0:
self.start -= 1
return self.start
else:
raise StopIteration
for x in DownCounter(5):
print(x,end = " ")
#--------------------------------------------
# 4 3 2 1 0
三、应用场景
生成器的三种应用场景
(1)定义一个容器类的可迭代对象,为该对象实现 __iter__
接口
(2)定义一个处理其它可迭代对象的迭代器
(3)定义一个不依赖数据存储的数据生成器
示例1
未用关键字 yield 时
class MyCustomDataIterator:
def __init__(self,data):
self.data = data
self.index = -1
def __iter__(self):
return self
def __next__(self):
self.index += 1
if self.index < self.data.size:
return self.data.get_value(self.index)
else:
raise StopIteration
class MyCustomData:
# 其余部分代码不重要都略过
...
@property
def size(self): # 假设可以得到数据的大小
return self.size
def get_value(self,index): # 假设可通过索引按顺序得到数据
return index
def __iter__(self): # 重构迭代器
return MyCustomDataIterator(self)
使用关键字 yield 后
class MyCustomData:
# 其余部分代码不重要都略过
...
@property
def size(self):
return self.size
def get_value(self,index):
return index
def __iter__(self):
# 注意:必须是局部变量,如果还用 self.index 就会认为指向了 可迭代对象!
index = -1
while index < 2:
index += 1
yield self.get_value(index)
示例2
未用关键字 yield 时
BLACK_LIST = ['白嫖', '取关']
class Suzhi_Iterator:
def __init__(self, action):
self.actions = actions
self.index = 0
def __next__(self):
while self.index < len(self.actions):
action = self.actions[self.index]
self.index += 1
if action in BLACK_LIST:
continue
elif '币' in action:
return action * 2
else:
return action
raise StopIteration
def __iter__(self):
return self
actions = ['点赞', '投币', '取关']
sz_iterator = Suzhi_Iterator(actions)
for x in Suzhi_Iterator(actions):
print(x)
使用关键字 yield 后
BLACK_LIST = ['白嫖', '取关']
def suzhi(actions):
for action in actions:
if action in BLACK_LIST:
continue
elif '币' in action:
yield action * 2
else:
yield action
actions = ['点赞', '投币', '取关']
for x in suzhi(actions):
print(x)
my_list2 = [x *x for x in range(5)]
for v in my_list2:
print(v,end = " ")
print("\n")