python:yield的作用

391 阅读5分钟

参考网址

www.yisu.com/zixun/22473…

含有yield关键字的函数,是一个生成器函数,返回的结果叫做生成器。

前言

yield是python的函数中的很常用的一个关键字。
他的作用是返回一个可以用来迭代(循环)的生成器,
它的应用场景通常是:需要返回一系列值的,含有循环的函数中

废话不多说,直接上demo
def f(): # 这就是生成器函数
    for x in range(4):
        yield x ** 2
        
square_gen = f()  # 生成器函数的返回值,是生成器,也叫做迭代器

for x in square_gen:
    print(x)

# 如上述代码所示
# 定义了一个函数f(),在该函数中有for循环,同时有yield关键字。有了yield关键字,不需要return语句
# 该函数的返回就是:一个可以迭代(循环)的生成器(生成的容器)square_gen
# 补充下:像list, 元组,map等,这些也是容器,从字面上理解,容器就是装了很多东西的瓶子,罐子。

# 生成器是可以被迭代访问的。
for x in square_gen():
    print(x) # 这里就是迭代访问生成器中的元素

如果想要彻底搞懂yield关键词的用法,你务必先要搞懂如下词的意思:迭代,可迭代,迭代器,生成器

什么是迭代和可迭代

迭代是一种操作,是一个动作,就是一个一个的去读取容器中的元素。

比如列表(list),字典(dict),元组(tuple)等,它们都是容器。
当我们在实际中使用这些容器的时候,常常需要逐一从里面获取数据,例如说,我们常用for...in...来打印列表中数据,而这种逐个获取数据的过程就叫做迭代,其实就是我们常说的循环。
a_list = [123]
for i in a_list:
    print(i)
可迭代则是对象的一种特性,代表某个对象具有某个特性。
比如说列表(list)对象是可迭代的,元组(tuple)对象是可迭代的,
字典(dict)对象是可迭代的,字符串对象(string)是可迭代的,file对象也是可以迭代的。
因为我们都可以从这些数据类型中逐一获取数据。

什么是迭代器

迭代器是一个对象,这种对象每次只能调取一个数据元素,这就是迭代器的特性。

对迭代器不断调用 next() 方法(将迭代器变量放入next()中当参数),则可以依次获取下一个元素;
当迭代器中没有元素时,调用 next() 方法会抛出 StopIteration(停止迭代) 异常。
迭代器的 __iter__() 方法返回迭代器自身;因此迭代器也是可迭代的。

def liebiao():
    for x in range(10):
    yield x
 
g = liebiao()

#0
print(next(g))
 
#1
print(next(g))
 
#2
print(next(g))
 
#3
print(next(g))
 
#4
print(next(g))
 
#<generator object liebiao at 0x02C70E70>
g.__iter__()

生成器函数(generation function) 和 生成器(generation)

生成器函数是一种特殊的函数,它的函数内部含有yield表达式,调用它会返回一个特殊的迭代器,称生成器。
def func():  # 这是1个普通的函数
    return 1
 
def gen():   # 这是1个生成器函数
    yield 1

# 从类型上看,二者都是函数
print(type(func))   # <class 'function'>
print(type(gen))    # <class 'function'>

# 但是从函数的结果上看,一个是int,另一个是generator,即生成器
print(type(func())) # <class 'int'>
print(type(gen()))  # <class 'generator'>

yeild表达式

如前所述,如果一个函数定义中包含 yield 表达式,那么该函数是一个生成器函数(而非普通函数)。
实际上,yield 仅能用于定义生成器函数。
生成器函数的返回值,叫做生成器。

与普通函数不同,生成器函数被调用后,其函数体内的代码并不会立即执行,而是返回一个生成器(generator-iterator)。
当返回的生成器调用成员方法时,相应的生成器函数中的代码才会执行。

换个说法

如果你看不懂生成器函数(不是普通的函数),也就是带有yield关键字的函数,那么你可以这样去理解:
    在函数开始处,加入 result = list();
    将每个 yield 表达式 yield expr 替换为 result.append(expr);
    在函数末尾处,加入 return result。

也就是说,yield的本质功能还是返回了一个可供迭代的列表对象。

def f():
    result = list()
    for i in range(10):
        result.append(i)
    return result

yield的好处

介绍了这么多定义和用法,那么到底为什么要用yield呢?它有什么样的好处呢?

在很多时候,我们需要逐个去获取容器内的某些数据,而这种仅仅获取部分元素的情况,并不需要我们将容器内所有的元素都取出来。
比如说一个容器内现有10000个元素,但我们只需要前5个元素,那么解决办法通常由如下两种:
   <1>获取容器内的所有元素,然后取出前 5 个;
   <2>从头开始,逐个迭代容器内的元素,迭代 5 个元素之后停止。

显而易见,如果容器内的元素数量非常多(比如有 10 ** 8 个),或者容器内的元素体积非常大,那么后一种方案能节省巨大的时间、空间开销。

现在假设,我们有一个函数,其产出(返回值)是一个列表。
而若我们知道,调用者对该函数的返回值,只有逐个迭代这一种方式。
那么,如果函数生产列表中的每一个元素都需要耗费非常多的时间,或者生成所有元素需要等待很长时间,则使用 yield 把函数变成一个生成器函数,每次只产生一个元素,就能节省很多开销了。