生成器详解——简单生成器

  • 一、生成器(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")