Python进阶——如何正确使用yield?

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"阅读本文大约需要 10 分钟。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Python 开发中,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 关键字的使用其实较为频繁,例如大集合的生成,简化代码结构、协程与并发都会用到它。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是,你是否真正了解 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 的运行过程呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这篇文章,我们就来看一下 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 的运行流程,以及在开发中哪些场景适合使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"生成器","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果在一个方法内,包含了 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 关键字,那么这个函数就是一个「生成器」。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"生成器其实就是一个特殊的迭代器,它可以像迭代器那样,迭代输出方法内的每个元素。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你还不清楚「迭代器」是什么,可以参考我写的这篇文章:","attrs":{}},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/21c4bdc50e084b08dff0e40dc","title":""},"content":[{"type":"text","text":"Python技术进阶——迭代器、可迭代对象、生成器","attrs":{}}]},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们来看一个包含 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 关键字的方法:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"# coding: utf8\n\n# 生成器\ndef gen(n):\n for i in range(n):\n yield i\n\ng = gen(5) # 创建一个生成器\nprint(g) # \nprint(type(g)) # \n\n# 迭代生成器中的数据\nfor i in g:\n print(i)\n \n# Output:\n# 0 1 2 3 4","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注意,在这个例子中,当我们执行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"g = gen(5)","attrs":{}}],"attrs":{}},{"type":"text","text":" 时,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"gen","attrs":{}}],"attrs":{}},{"type":"text","text":" 中的代码其实并没有执行,此时我们只是创建了一个「生成器对象」,它的类型是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"generator","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然后,当我们执行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"for i in g","attrs":{}}],"attrs":{}},{"type":"text","text":",每执行一次循环,就会执行到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 处,返回一次 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 后面的值。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这个迭代过程是和迭代器最大的区别。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"换句话说,如果我们想输出 5 个元素,在创建生成器时,这个 5 个元素其实还并没有产生,什么时候产生呢?只有在执行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"for","attrs":{}}],"attrs":{}},{"type":"text","text":" 循环遇到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 时,才会依次生成每个元素。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此外,生成器除了和迭代器一样实现迭代数据之外,还包含了其他方法:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"generator.__next__()","attrs":{}}],"attrs":{}},{"type":"text","text":":执行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"for","attrs":{}}],"attrs":{}},{"type":"text","text":" 时调用此方法,每次执行到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 就会停止,然后返回 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 后面的值,如果没有数据可迭代,抛出 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"StopIterator","attrs":{}}],"attrs":{}},{"type":"text","text":" 异常,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"for","attrs":{}}],"attrs":{}},{"type":"text","text":" 循环结束","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"generator.send(value)","attrs":{}}],"attrs":{}},{"type":"text","text":":外部传入一个值到生成器内部,改变 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 前面的值","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"generator.throw(type[, value[, traceback]])","attrs":{}}],"attrs":{}},{"type":"text","text":":外部向生成器抛出一个异常","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"generator.close()","attrs":{}}],"attrs":{}},{"type":"text","text":":关闭生成器","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通过使用生成器的这些方法,我们可以完成很多有意思的功能。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"__","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"next","attrs":{}},{"type":"text","text":"__","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先来看生成器的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"__next__","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法,我们看下面这个例子。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"# coding: utf8\n\ndef gen(n):\n for i in range(n):\n print('yield before')\n yield i\n print('yield after')\n\ng = gen(3) # 创建一个生成器\nprint(g.__next__()) # 0\nprint('----')\nprint(g.__next__()) # 1\nprint('----')\nprint(g.__next__()) # 2\nprint('----')\nprint(g.__next__()) # StopIteration\n\n# Output:\n# yield before\n# 0\n# ----\n# yield after\n# yield before\n# 1\n# ----\n# yield after\n# yield before\n# 2\n# ----\n# yield after\n# Traceback (most recent call last):\n# File \"gen.py\", line 16, in \n# print(g.__next__()) # StopIteration\n# StopIteration","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在这个例子中,我们定义了 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"gen","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法,这个方法包含了 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 关键字。然后我们执行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"g = gen(3)","attrs":{}}],"attrs":{}},{"type":"text","text":" 创建一个生成器,但是这次没有执行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"for","attrs":{}}],"attrs":{}},{"type":"text","text":" 去迭代它,而是多次调用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"g.","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"next","attrs":{}},{"type":"text","text":"()","attrs":{}}],"attrs":{}},{"type":"text","text":" 去输出生成器中的元素。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们看到,当执行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"g.__next__()","attrs":{}}],"attrs":{}},{"type":"text","text":"时,代码就会执行到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 处,然后返回 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 后面的值,如果继续调用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"g.__next__()","attrs":{}}],"attrs":{}},{"type":"text","text":",注意,你会发现,这次执行的开始位置,是上次 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 结束的地方,并且它还保留了上一次执行的上下文,继续向后迭代。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这就是使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 的作用,在迭代生成器时,每一次执行都可以保留上一次的状态,而不是像普通方法那样,遇到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"return","attrs":{}}],"attrs":{}},{"type":"text","text":" 就返回结果,下一次执行只能再次重复上一次的流程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"生成器除了能保存状态之外,我们还可以通过其他方式,改变其内部的状态,这就是下面要讲的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"send","attrs":{}}],"attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"throw","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"send","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面的例子中,我们只展示了在 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 后有值的情况,其实还可以使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"j = yield i","attrs":{}}],"attrs":{}},{"type":"text","text":" 这种语法,我们看下面的代码:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"# coding: utf8\n\ndef gen():\n i = 1\n while True:\n j = yield i\n i *= 2\n if j == -1:\n break","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此时如果我们执行下面的代码:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"for i in gen():\n print(i)\n time.sleep(1)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"输出结果会是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1 2 4 8 16 32 64 ...","attrs":{}}],"attrs":{}},{"type":"text","text":" 一直循环下去, 直到我们杀死这个进程才能停止。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这段代码一直循环的原因在于,它无法执行到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"j == -1","attrs":{}}],"attrs":{}},{"type":"text","text":" 这个分支里 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"break","attrs":{}}],"attrs":{}},{"type":"text","text":" 出来,如果我们想让代码执行到这个地方,如何做呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里就要用到生成器的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"send","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法了,","attrs":{}},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"send","attrs":{}}],"attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":" 方法可以把外部的值传入生成器内部,从而改变生成器的状态。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代码可以像下面这样写:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"g = gen() # 创建一个生成器\nprint(g.__next__()) # 1\nprint(g.__next__()) # 2\nprint(g.__next__()) # 4\n# send 把 -1 传入生成器内部 走到了 j = -1 这个分支\nprint(g.send(-1)) # StopIteration 迭代停止","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当我们执行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"g.send(-1)","attrs":{}}],"attrs":{}},{"type":"text","text":" 时,相当于把 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"-1","attrs":{}}],"attrs":{}},{"type":"text","text":" 传入到了生成器内部,然后赋值给了 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 前面的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"j","attrs":{}}],"attrs":{}},{"type":"text","text":",此时 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"j = -1","attrs":{}}],"attrs":{}},{"type":"text","text":",然后这个方法就会 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"break","attrs":{}}],"attrs":{}},{"type":"text","text":" 出来,不会继续迭代下去。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"throw","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"外部除了可以向生成器内部传入一个值外,还可以传入一个异常,也就是调用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"throw","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"# coding: utf8\n\ndef gen():\n try:\n yield 1\n except ValueError:\n yield 'ValueError'\n finally:\n print('finally')\n\ng = gen() # 创建一个生成器\nprint(g.__next__()) # 1\n# 向生成器内部传入异常 返回ValueError\nprint(g.throw(ValueError))\n\n# Output:\n# 1\n# ValueError\n# finally","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这个例子创建好生成器后,使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"g.throw(ValueError)","attrs":{}}],"attrs":{}},{"type":"text","text":" 的方式,向生成器内部传入了一个异常,走到了生成器异常处理的分支逻辑。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"close","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"生成器的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"close","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法也比较简单,就是手动关闭这个生成器,关闭后的生成器无法再进行操作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":">>> g = gen()\n>>> g.close() # 关闭生成器\n>>> g.__next__() # 无法迭代数据\nTraceback (most recent call last):\n File \"\", line 1, in \nStopIteration","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"close","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法我们在开发中使用得比较少,了解一下就好。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"使用场景","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"了解了 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 和生成器的使用方式,那么 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 和生成器一般用在哪些业务场景中呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面我介绍几个例子,分别是大集合的生成、简化代码结构、协程与并发,你可以参考这些使用场景来使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"大集合的生成","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你想生成一个非常大的集合,如果使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"list","attrs":{}}],"attrs":{}},{"type":"text","text":" 创建一个集合,这会导致在内存中申请一个很大的存储空间,例如想下面这样:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"# coding: utf8\n\ndef big_list():\n result = []\n for i in range(10000000000):\n result.append(i)\n return result\n\n# 一次性在内存中生成大集合 内存占用非常大\nfor i in big_list():\n print(i)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这种场景,我们使用生成器就能很好地解决这个问题。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因为生成器只有在执行到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 时才会迭代数据,这时只会申请需要返回元素的内存空间,代码可以这样写:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"# coding: utf8\n\ndef big_list():\n for i in range(10000000000):\n yield i\n\n# 只有在迭代时 才依次生成元素 减少内存占用\nfor i in big_list():\n print(i)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"简化代码结构","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们在开发时还经常遇到这样一种场景,如果一个方法要返回一个 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"list","attrs":{}}],"attrs":{}},{"type":"text","text":",但这个 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"list","attrs":{}}],"attrs":{}},{"type":"text","text":" 是多个逻辑块组合后才能产生的,这就会导致我们的代码结构变得很复杂:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"# coding: utf8\n\ndef gen_list():\n # 多个逻辑块 组成生成一个列表\n result = []\n for i in range(10):\n result.append(i)\n for j in range(5):\n result.append(j * j)\n for k in [100, 200, 300]:\n result.append(k)\n return result\n \nfor item in gen_list():\n print(item)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这种情况下,我们只能在每个逻辑块内使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"append","attrs":{}}],"attrs":{}},{"type":"text","text":" 向 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"list","attrs":{}}],"attrs":{}},{"type":"text","text":" 中追加元素,代码写起来比较啰嗦。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此时如果使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 来生成这个 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"list","attrs":{}}],"attrs":{}},{"type":"text","text":",代码就简洁很多:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"# coding: utf8\n\ndef gen_list():\n # 多个逻辑块 使用yield 生成一个列表\n for i in range(10):\n yield i\n for j in range(5):\n yield j * j\n for k in [100, 200, 300]:\n yield k\n \nfor item in gen_list():\n print(i)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 后,就不再需要定义 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"list","attrs":{}}],"attrs":{}},{"type":"text","text":" 类型的变量,只需在每个逻辑块直接 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 返回元素即可,可以达到和前面例子一样的功能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们看到,使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 的代码更加简洁,结构也更清晰,另外的好处是只有在迭代元素时才申请内存空间,降低了内存资源的消耗。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"协程与并发","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"还有一种场景是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 使用非常多的,那就是「协程与并发」。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果我们想提高程序的执行效率,通常会使用多进程、多线程的方式编写程序代码,最常用的编程模型就是「生产者-消费者」模型,即一个进程 / 线程生产数据,其他进程 / 线程消费数据。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在开发多进程、多线程程序时,为了防止共享资源被篡改,我们通常还需要加锁进行保护,这样就增加了编程的复杂度。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Python 中,除了使用进程和线程之外,我们还可以使用「协程」来提高代码的运行效率。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"什么是协程?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"简单来说,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"由多个程序块组合协作执行的程序,称之为「协程」。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而在 Python 中使用「协程」,就需要用到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 关键字来配合。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可能这么说还是太好理解,我们用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 实现一个协程生产者、消费者的例子:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"# coding: utf8\n\ndef consumer():\n i = None\n while True:\n # 拿到 producer 发来的数据\n j = yield i \n print('consume %s' % j)\n\ndef producer(c):\n c.__next__()\n for i in range(5):\n print('produce %s' % i)\n # 发数据给 consumer\n c.send(i)\n c.close()\n\nc = consumer()\nproducer(c)\n\n# Output:\n# produce 0\n# consume 0\n# produce 1\n# consume 1\n# produce 2\n# consume 2\n# produce 3\n# consume 3\n...","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这个程序的执行流程如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"c = consumer()","attrs":{}}],"attrs":{}},{"type":"text","text":" 创建一个生成器对象","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"producer(c)","attrs":{}}],"attrs":{}},{"type":"text","text":" 开始执行,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"c.__next()__","attrs":{}}],"attrs":{}},{"type":"text","text":" 会启动生成器 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"consumer","attrs":{}}],"attrs":{}},{"type":"text","text":" 直到代码运行到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"j = yield i","attrs":{}}],"attrs":{}},{"type":"text","text":" 处,此时 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"consumer","attrs":{}}],"attrs":{}},{"type":"text","text":" 第一次执行完毕,返回","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"producer","attrs":{}}],"attrs":{}},{"type":"text","text":" 函数继续向下执行,直到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"c.send(i)","attrs":{}}],"attrs":{}},{"type":"text","text":" 处,这里利用生成器的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"send","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法,向 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"consumer","attrs":{}}],"attrs":{}},{"type":"text","text":" 发送数据","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"consumer","attrs":{}}],"attrs":{}},{"type":"text","text":" 函数被唤醒,从 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"j = yield i","attrs":{}}],"attrs":{}},{"type":"text","text":" 处继续开始执行,并且接收到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"producer","attrs":{}}],"attrs":{}},{"type":"text","text":" 传来的数据赋值给 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"j","attrs":{}}],"attrs":{}},{"type":"text","text":",然后打印输出,直到再次执行到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 处,返回","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"producer","attrs":{}}],"attrs":{}},{"type":"text","text":" 继续循环执行上面的过程,依次发送数据给 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cosnumer","attrs":{}}],"attrs":{}},{"type":"text","text":",直到循环结束","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"最终 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"c.close()","attrs":{}}],"attrs":{}},{"type":"text","text":" 关闭 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"consumer","attrs":{}}],"attrs":{}},{"type":"text","text":" 生成器,程序退出","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在这个例子中我们发现,程序在 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"producer","attrs":{}}],"attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"consumer","attrs":{}}],"attrs":{}},{"type":"text","text":" 这 2 个函数之间","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"来回切换","attrs":{}},{"type":"text","text":"执行,相互协作,完成了生产任务、消费任务的业务场景,最重要的是,整个程序是在","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"单进程单线程","attrs":{}},{"type":"text","text":"下完成的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这个例子用到了上面讲到的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":"、生成器的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"__next__","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"send","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"close","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法。如果不好理解,你可以多看几遍这个例子,最好自己测试一下。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们使用协程编写生产者、消费者的程序时,它的好处是:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"整个程序运行过程中无锁,不用考虑共享变量的保护问题,降低了编程复杂度","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"程序在函数之间来回切换,这个过程是用户态下进行的,不像进程 / 线程那样,会陷入到内核态,这就减少了内核态上下文切换的消耗,执行效率更高","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Python 的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"yield","attrs":{}}],"marks":[{"type":"strong"}],"attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":" 和生成器实现了协程的编程方式,为程序的并发执行提供了编程基础。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Python 中的很多第三方库,都是基于这一特性进行封装的,例如 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"gevent","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"tornado","attrs":{}}],"attrs":{}},{"type":"text","text":",它们都大大提高了程序的运行效率。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"总结","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"总结一下,这篇文章我们主要讲了 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 的使用方式,以及生成器的各种特性。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"生成器是一种特殊的迭代器,它除了可以迭代数据之外,在执行时还可以保存方法中的状态,除此之外,它还提供了外部改变内部状态的方式,把外部的值传入到生成器内部。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"利用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 和生成器的特性,我们在开发中可以用在大集成的生成、简化代码结构、协程与并发的业务场景中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Python 的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 也是实现协程和并发的基础,它提供了协程这种用户态的编程模式,提高了程序运行的效率。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c1/c10b027be63b0c5c1ffc36a76a4a974b.png","alt":null,"title":"","style":[{"key":"width","value":"25%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"关注「水滴与银弹」公众号,7年资深后端研发,和你分享更多优质技术干货。","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章