生成器 yield

<meta charset="utf-8">

作者:Jackpop
链接:https://www.zhihu.com/question/345210030/answer/944246758
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

要想理解yield的用法和用途,最好的方式还是应该系统的了解一下可迭代对象、迭代器、生成器,这样会更加容易yield所处的位置及价值。下面我就来由浅入深,逐步讲解一下关键字yield的用法和作用。

可迭代对象

<noscript>
image

</noscript>

image

在讲解迭代器和生成器之前,先介绍一下可迭代对象。

可迭代对象是Python中一个非常庞大的概念,它主要包括如下三类:

  • 迭代器
  • 序列
  • 字典

从上图可以看出不同概念之间的关系,迭代器是可迭代对象的一个子集,而生成器又是迭代器的一个子集,是一种特殊的迭代器。除了迭代器之外,Python中还有序列、字典等可迭代对象。

现在已经直观的了解了可迭代对象与迭代器、生成器之间的关系,那么用Python语言怎么表述它们的区别呢?

  • 可迭代对象需要实现iter方法
  • 迭代器不仅要实现iter方法,还需要实现next方法

在使用层面,可迭代对象可以通过innot in访问对象中的元素,举一个例子,

X = set([1,2,3,4,5])
print(X)
print(type(X))
print(1 in X)
print(2 not in X)
for x in X:
    print(x)

# 输出
{1, 2, 3, 4, 5}
<class 'set'>
True
False
1
2
3
4
5

前面提到,可迭代对象实现了iter方法,但是它没有实现next,这也是判定迭代器和其他可迭代对象的关键之处,可以看一下通过next访问上述示例中可迭代对象X会报错,

next(X)
​
# 输出
TypeError: 'set' object is not an iterator

报的错误是'set' object is not an iterator,它指明了set集合是一个可迭代对象,但不是迭代器,下面就来介绍一下迭代器。

迭代器

迭代器是可迭代对象的一个子集,它是一个可以记住遍历的位置的对象,它与列表、元组、集合、字符串这些可迭代对象的区别就在于next方法的实现,其他列表、元组、集合、字符串这些可迭代对象可以很简单的转化成迭代器,通过Python内置的iter函数能够轻松把可迭代对象转化为迭代器,下面来看一个例子,

X = [1,2,3,4,5]
print(type(X))
Y = iter(X)
print(type(Y))
print(next(Y))
print(next(Y))
print(next(Y))
​
# 输出
<class 'list'>
<class 'list_iterator'>
1
2
3

从上述示例中我们可以看出两点:

  • 通过iter函数把list转化成了迭代器
  • 可迭代器能够记住遍历位置,能够通过next方法不断从前往后访问

除了Python内置的iter之外,还可以通过Python内置的工具包itertools创建迭代器,其中函数包括,

  • count
  • cycle
  • repeat
  • accumulate
  • chain
  • compress
  • dropwhile
  • islice
  • product
  • permutations
  • combinations
  • ......

itertools中包含很多用于创建迭代器的实用方法,如果感兴趣嗯可以访问官方文档进行详细了解。

当然,也可以自己通过实现iternext方法来定义迭代器,

class Iterator(object):
    def __init__(self, array):
        self.x = array
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index < len(self.x):
            value = self.x[self.index]
            self.index += 1
        else:
            raise StopIteration
        return value

it = Iterator([1,2,3,4,5])
print(type(it))
for i in it:
    print(i)
​
# 输出
<class '__main__.Iterator'>
1
2
3
4
5

生成器—yield

从文章开头的流程图可以直观的看出,生成器是迭代器的子集,换句话说,生成器一定是迭代器,但是迭代器不全是生成器对象。

提及生成器就不得不提及一个Python中的关键字yield,在Python中一个函数可以用yield替代return返回值,这样的话这个函数就变成了一个生成器对象,举个例子对比一下,

def generator(array):
    for i in array:
        return i

gen = generator([1,2,3,4,5])
print(type(gen))
​
# 输出
<class 'int'>

这是我们常见的return返回方式,这样的话generator函数获取的是一个int型对象,下面看一下换成yield关键字,

def generator(array):
    for i in array:
        yield(i)

gen = generator([1,2,3,4,5])
print(type(gen))
​
# 输出
<class 'generator'>

这样的话获取的是一个生成器generator,除了yield之外,在Python3.3之后还加入了yield from获取生成器,允许一个生成器将其部分操作委派给另一个生成器,使得生成器的用法变得更加简洁,yield from后面需要加上可迭代对象,这样可以把可迭代对象变成生成器,当然,这里的可迭代对象不仅包含列表、元组,还包含迭代器、生成器。yield from相对于yield的有几个主要优点:

  • 代码更加简洁
  • 可以用于生成器嵌套
  • 易于异常处理

下面就从简洁代码方面举个例子说明一下,

def generator(array):
    for sub_array in array:
        yield from sub_array
​
gen = generator([(1,2,3), (4,5,6,7)])
​
# 输出
1
2
3
4
5
6
7

当我们需要访问多层/多维可迭代对象时,我们就不需要逐层的去用for ... in ...去访问,可以简单的通过yiled from把生成器委派给子生成器,除此之外还可以通过生成器表达式的方法得到生成式,后面会介绍。

print(next(gen))
print(next(gen))
​
# 输出
1
2

通过上面示例可以看出,生成器可以像迭代器那样使用iter和next方法。

读到这里可以会有疑惑,从这个示例看来生成器和迭代器并没有什么区别啊?为什么生成器还可以称得上是Python中的一大亮点?

首先它对比于迭代器在编码方面更加简洁,这是显而易见的,其次生成器运行速度更快,最后一点,也是需要着重说明的一点:节省内存。

也许在一些理论性实验、学术论文阶段可以不考虑这些工程化的问题,但是在公司做项目时,内存和资源占用是无法逃避的问题 。如果我们使用其他可迭代对象处理庞大的数据时,当创建或者返回值时会申请用于存储整个可迭代对象的内存,显然这是非常浪费的,因为有的元素当前我们用不到,也不会去访问,但它却一直占用这内存。这时候就体现了生成器的优点,它不是一次性把所有的结果都返回,而是当我们每读取一次,它会返回一个结果,当我们不读取时,它就是一个生成器表达式,几乎不占用内存。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343