浅谈生成器yield
By 青衣极客 Blue Geek In 2019-11-10
生成器似乎并不是一个经常被开发者讨论的语法,因此也就没有它的大兄弟迭代器那么著名。大家不讨论它并不是说大家都已经对它熟悉到人尽皆知,与之相反,即使是工作多年的开发者可能对生成器的运行过程还是知之甚少。这是什么原因导致的呢?我猜想大概有以下几点原因:(1)运行流程不同寻常,(2)日常开发不需要,(3)常常将生成器与迭代器混淆。生成器的运行流程可以按照协程来理解,也就是说返回中间结果,断点继续运行。这与我们通常对于程序调用的理解稍有差异。这种运行模式是针对什么样的需求呢?一般而言,生成器是应用于大量磁盘资源的处理。比如一个很大的文件,每次读取一行,下一次读取需要以上一次读取的位置为基础。下面就通过代码演示具体看看生成器的运行机制、使用方式以及与迭代器的比较。
1. 什么是生成器
什么是生成器?直接用文字描述可能太过抽象,倒不如先运行一段代码,分析这段代码的运行流程,然后总结出自己对生成器的理解。
def func():
print('进入函数')
yield '第一次返回值'
print('继续运行')
yield '第二次返回值'
ret = func()
print(ret)
print(next(ret))
print(next(ret))
<generator object func at 0x105167950>
进入函数
第一次返回值
继续运行
第二次返回值
从以上演示可以看出,这段代码定义了一个函数,这个函数除了yield这个关键字之外与一般函数并没有差异,也就是说生成器的魔法都是这个yield关键字引起的。第一点,函数的返回值是一个生成器对象。上述代码中,直接调用这个看似普通的函数,然后将返回值打印出来,发现返回值是一个对象,而并不是普通函数的返回值。第二点,可以使用next对这个生成器对象进行操作。生成器对象天然的可以被next函数调用,然后返回在yield关键字后面的内容。第三,再次调用next函数处理生成器对象,发现是从上次yield语句之后继续运行,直到下一个yield语句返回。
2. 加强版生成器
生成器的运行流程确实诡异,下面还要演示一个生成器可以执行的更加诡异的操作:运行过程中向函数传参。
def func():
print('进入函数')
value = (yield '第一次返回值')
if value is not None:
print('传入生成器的参数', value)
print('继续运行')
yield '第二次返回值'
ret = func()
print(ret)
print(next(ret))
print(ret.send('hello'))
<generator object func at 0x10529fc50>
进入函数
第一次返回值
传入生成器的参数 hello
继续运行
第二次返回值
返回生成器和next函数操作生成器已经并不奇怪了,但是在函数运行过程中向其传参还是让人惊呆了。调用生成器的send函数传入参数,在函数内使用yield语句的返回值接收,然后继续运行直到下一个yield语句返回。以前实现这种运行流程的方式是在函数中加上一个从控制台获取数据的指令,或者提前将参数传入,但是现在不用了,send方式使得传入的参数可以随着读取到的参数变化而变化。
3. 生成器的大兄弟:迭代器
很多的开发者比较容易混淆生成器和迭代器,而迭代器的运行过程更加符合一般的程序调用运行流程,因此从亲进度和使用熟悉度而言,大家对迭代器更有好感。比如下面演示一个对迭代器使用next方法进行操作。
# 迭代器
a = [1,2,3,4,5]
ret = iter(a)
print(next(ret))
print(next(ret))
1
2
从以上演示来看,大家或许会认为迭代器比生成器简单易用得太多了。不过,如果你了解迭代器的实现机制,可能就不会这么早下结论了。python内置了一些已经实现了的迭代器使用确实方便,但是如果需要自己去写一个迭代器呢?下面这段代码就带大家见识以下迭代器的实现。
class SeqIter:
def __init__(self, values):
self.values = values
self.idx = 0
def __iter__(self):
return self
def __next__(self):
self.idx += 1
if self.idx > len(self.values):
raise StopIteration()
else:
return self.values[self.idx-1]
seq_iter = SeqIter([1,2,3,4,5])
print(next(seq_iter))
print(next(seq_iter))
for i in seq_iter:
print('后续元素', i)
1
2
后续元素 3
后续元素 4
后续元素 5
在python中,能被next函数操作的对象一定带有__next__函数的实现,而能够被迭代的对象有必须实现__iter__函数。看了这么一段操作,相信大家对迭代器实现的繁琐也是深有体会了,那么生成器的实现是不是会让你觉得更加简单易用呢?不过千万别产生一个误区,即生成器比迭代器简单就多用生成器。在实际开发中,如果遇到与大量磁盘文件或者数据库操作相关的倒是可以使用生成器。但是在其他的任务中使用生成器难免有炫技,并且使逻辑不清晰而导致可读性下降的嫌疑。这大概也能解释生成器受冷落的原因。不过作为一个专业的开发者,熟悉语言特性是分内之事。
到此,关于生成器的讨论就结束了。

COMMENT
博客评论区功能由Github Issue提供,提交Issue时请以本文标题为话题。
"BG47-浅谈生成器yield"