Python 的“yield”关键字有哪些应用?
来源:stackoverflow
时间:2024-03-13 23:51:27 211浏览 收藏
哈喽!大家好,很高兴又见面了,我是golang学习网的一名作者,今天由我给大家带来一篇《Python 的“yield”关键字有哪些应用?》,本文主要会讲到等等知识点,希望大家一起学习进步,也欢迎大家关注、点赞、收藏、转发! 下面就一起来看看吧!
python 中 yield
关键字有什么用?它有什么作用?
例如,我试图理解这段代码1:
def _get_child_candidates(self, distance, min_dist, max_dist): if self._leftchild and distance - max_dist < self._median: yield self._leftchild if self._rightchild and distance + max_dist >= self._median: yield self._rightchild
这是调用者:
result, candidates = [], [self] while candidates: node = candidates.pop() distance = node._get_dist(obj) if distance <= max_dist and distance >= min_dist: result.extend(node._values) candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) return result
调用 _get_child_candidates
方法时会发生什么?
是否返回列表?单一元素?又叫了吗?后续调用什么时候停止?
正确答案
要了解 yield
的用途,您必须了解生成器是什么。在了解生成器之前,您必须了解可迭代对象。
可迭代
当您创建列表时,您可以一项一项地读取其项目。一项一项地读取其项称为迭代:
>>> mylist = [1, 2, 3] >>> for i in mylist: ... print(i) 1 2 3
mylist
是一个可迭代。当您使用列表理解时,您创建了一个列表,因此创建了一个可迭代对象:
>>> mylist = [x*x for x in range(3)] >>> for i in mylist: ... print(i) 0 1 4
您可以使用“for... in...
”的所有内容都是可迭代的; lists
、strings
、文件...
这些迭代器很方便,因为您可以随意读取它们,但是您将所有值存储在内存中,当您有很多值时,这并不总是您想要的。
生成器
生成器是迭代器,是一种可迭代的只能迭代一次。生成器不会将所有值存储在内存中,它们会动态生成值:
>>> mygenerator = (x*x for x in range(3)) >>> for i in mygenerator: ... print(i) 0 1 4
除了使用 ()
而不是 []
之外,它是相同的。但是,您不能在 mygenerator 中第二次执行 for i ,因为生成器只能使用一次:它们计算 0,然后忘记它并计算 1,并在计算 4 后一一结束。< /p>
产量
yield
是一个关键字,其使用方式与 return
类似,但该函数将返回一个生成器。
>>> def create_generator(): ... mylist = range(3) ... for i in mylist: ... yield i*i ... >>> mygenerator = create_generator() # create a generator >>> print(mygenerator) # mygenerator is an object!>>> for i in mygenerator: ... print(i) 0 1 4
这是一个无用的示例,但是当您知道您的函数将返回一组只需读取一次的巨大值时,它会很方便。
要掌握yield
,你必须明白当你调用该函数时,你在函数体中编写的代码不会运行。该函数只返回生成器对象,这有点棘手.
然后,每次 for
使用生成器时,您的代码将从上次停止的位置继续。
现在是困难的部分:
第一次 for
调用从函数创建的生成器对象时,它将从头开始运行函数中的代码,直到遇到 yield
,然后返回循环的第一个值。然后,每个后续调用将运行您在函数中编写的循环的另一次迭代并返回下一个值。这将持续下去,直到生成器被认为是空的,当函数运行而没有命中 yield
时就会发生这种情况。这可能是因为循环已结束,或者因为您不再满足 “if/else”
。
您的代码解释
生成器:
# here you create the method of the node object that will return the generator def _get_child_candidates(self, distance, min_dist, max_dist): # here is the code that will be called each time you use the generator object: # if there is still a child of the node object on its left # and if the distance is ok, return the next child if self._leftchild and distance - max_dist < self._median: yield self._leftchild # if there is still a child of the node object on its right # and if the distance is ok, return the next child if self._rightchild and distance + max_dist >= self._median: yield self._rightchild # if the function arrives here, the generator will be considered empty # there are no more than two values: the left and the right children
调用者:
# create an empty list and a list with the current object reference result, candidates = list(), [self] # loop on candidates (they contain only one element at the beginning) while candidates: # get the last candidate and remove it from the list node = candidates.pop() # get the distance between obj and the candidate distance = node._get_dist(obj) # if the distance is ok, then you can fill in the result if distance <= max_dist and distance >= min_dist: result.extend(node._values) # add the children of the candidate to the candidate's list # so the loop will keep running until it has looked # at all the children of the children of the children, etc. of the candidate candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) return result
此代码包含几个智能部分:
循环在列表上迭代,但列表在循环迭代时扩展。这是一种遍历所有这些嵌套数据的简洁方法,即使它有点危险,因为您可能会陷入无限循环。在这种情况下,
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
耗尽了生成器的所有值,但是而
不断创建新的生成器对象,这些对象将产生与之前的值不同的值,因为它没有应用于同一节点。extend()
方法是一个列表对象方法,它需要一个可迭代对象并将其值添加到列表中。
通常,我们向其传递一个列表:
>>> a = [1, 2] >>> b = [3, 4] >>> a.extend(b) >>> print(a) [1, 2, 3, 4]
但是在您的代码中,它有一个生成器,这很好,因为:
- 您无需读取这些值两次。
- 您可能有很多孩子,但您不希望将它们全部存储在内存中。
它之所以有效,是因为 python 不关心方法的参数是否是列表。 python 需要可迭代,因此它可以与字符串、列表、元组和生成器一起使用!这称为鸭子类型,也是 python 如此酷的原因之一。但这是另一个故事,另一个问题......
您可以停在这里,或者阅读一点内容以了解生成器的高级用法:
控制发电机耗尽
>>> class bank(): # let's create a bank, building atms ... crisis = false ... def create_atm(self): ... while not self.crisis: ... yield "$100" >>> hsbc = bank() # when everything's ok the atm gives you as much as you want >>> corner_street_atm = hsbc.create_atm() >>> print(corner_street_atm.next()) $100 >>> print(corner_street_atm.next()) $100 >>> print([corner_street_atm.next() for cash in range(5)]) ['$100', '$100', '$100', '$100', '$100'] >>> hsbc.crisis = true # crisis is coming, no more money! >>> print(corner_street_atm.next())>>> wall_street_atm = hsbc.create_atm() # it's even true for new atms >>> print(wall_street_atm.next()) >>> hsbc.crisis = false # the trouble is, even post-crisis the atm remains empty >>> print(corner_street_atm.next()) >>> brand_new_atm = hsbc.create_atm() # build a new one to get back in business >>> for cash in brand_new_atm: ... print cash $100 $100 $100 $100 $100 $100 $100 $100 $100 ...
注意:对于 python 3,请使用print(corner_street_atm.__next__())
或 print(next(corner_street_atm))
它对于各种事情都很有用,例如控制对资源的访问。
itertools,你最好的朋友
itertools
模块包含用于操作可迭代的特殊函数。曾经想要复制一个生成器吗?
连接两个发电机?用单行代码将嵌套列表中的值分组? map / zip
而不创建另一个列表?
然后只需 import itertools
。
举个例子?让我们看看四匹马比赛可能的到达顺序:
>>> horses = [1, 2, 3, 4] >>> races = itertools.permutations(horses) >>> print(races)>>> print(list(itertools.permutations(horses))) [(1, 2, 3, 4), (1, 2, 4, 3), (1, 3, 2, 4), (1, 3, 4, 2), (1, 4, 2, 3), (1, 4, 3, 2), (2, 1, 3, 4), (2, 1, 4, 3), (2, 3, 1, 4), (2, 3, 4, 1), (2, 4, 1, 3), (2, 4, 3, 1), (3, 1, 2, 4), (3, 1, 4, 2), (3, 2, 1, 4), (3, 2, 4, 1), (3, 4, 1, 2), (3, 4, 2, 1), (4, 1, 2, 3), (4, 1, 3, 2), (4, 2, 1, 3), (4, 2, 3, 1), (4, 3, 1, 2), (4, 3, 2, 1)]
理解迭代的内部机制
迭代是一个隐含可迭代对象(实现 __iter__()
方法)和迭代器(实现 __next__()
方法)的过程。
可迭代对象是可以从中获取迭代器的任何对象。迭代器是允许您迭代可迭代对象的对象。
这篇关于 how for
loops work 的文章中有更多相关内容。
了解 yield
的捷径
当您看到带有 yield
语句的函数时,应用这个简单的技巧来了解会发生什么:
- 在函数开头插入一行
result = []
。 - 将每个
yield expr
替换为result.append(expr)
。 - 在函数底部插入一行
return result
。 - 耶 - 不再有
yield
语句!阅读并找出代码。 - 将函数与原始定义进行比较。
这个技巧可能会让您了解函数背后的逻辑,但 yield
实际发生的情况与基于列表的方法中发生的情况明显不同。在许多情况下,yield 方法的内存效率更高,速度也更快。在其他情况下,这个技巧会让你陷入无限循环,即使原始函数工作得很好。继续阅读以了解更多信息...
不要混淆可迭代对象、迭代器和生成器
首先,迭代器协议 - 当你编写时
for x in mylist: ...loop body...
python 执行以下两个步骤:
获取
mylist
的迭代器:调用
iter(mylist)
-> 这会返回一个带有next()
方法的对象(或 python 3 中的__next__()
)。[这是大多数人忘记告诉您的步骤]
使用迭代器循环遍历项目:
继续在步骤 1 返回的迭代器上调用
next()
方法。next()
的返回值被分配给x
并执行循环体。如果stopiteration
从next()
内部引发异常,则意味着迭代器中不再有值,并且退出循环。
事实上,python 在任何想要循环对象内容的时候都会执行上述两个步骤 - 所以它可以是一个 for 循环,但也可以是像 otherlist.extend( mylist)
(其中 otherlist
是一个 python 列表)。
这里 mylist
是一个可迭代的,因为它实现了迭代器协议。在用户定义的类中,您可以实现 __iter__()
方法以使类的实例可迭代。此方法应该返回一个迭代器。迭代器是具有 next()
方法的对象。可以在同一个类上实现 __iter__()
和 next()
,并让 __iter__()
返回 self
。这适用于简单的情况,但当您希望两个迭代器同时循环同一对象时则不适用。
这就是迭代器协议,许多对象都实现了这个协议:
- 内置列表、字典、元组、集合和文件。
- 实现
__iter__()
的用户定义类。 - 发电机。
请注意,for
循环不知道它正在处理哪种对象 - 它只是遵循迭代器协议,并且很乐意在调用 next()
时获取一个又一个的项目。内置列表一一返回其项目,字典一一返回键,文件一一返回行,等等。生成器返回......好吧这就是 yield
的用武之地:
def f123(): yield 1 yield 2 yield 3 for item in f123(): print item
如果 f123()
中有三个 return
语句,而不是 yield
语句,则只有第一个语句会被执行,并且该函数将退出。但 f123()
不是普通函数。当调用 f123()
时,它不会返回yield 语句中的任何值!它返回一个生成器对象。此外,该函数并没有真正退出——它进入挂起状态。当 for
循环尝试循环生成器对象时,该函数会在之前返回的 yield
之后的下一行从挂起状态恢复,执行下一行代码(在本例中为 yield
语句),并返回该语句作为下一个项目。这种情况会一直发生,直到函数退出,此时生成器引发 stopiteration
,并且循环退出。
所以生成器对象有点像一个适配器 - 一方面它展示了迭代器协议,通过公开 __iter__()
和 next()
方法来保持 for
循环快乐。然而,在另一端,它运行该函数足以从中获取下一个值并将其放回挂起模式。
为什么使用生成器?
通常,您可以编写不使用生成器但实现相同逻辑的代码。一种选择是使用我之前提到的临时列表“技巧”。这并不适用于所有情况,例如如果你有无限循环,或者当你有一个很长的列表时,它可能会导致内存使用效率低下。另一种方法是实现一个新的可迭代类 somethingiter
,它将状态保留在实例成员中,并在其 next()
(或 python 3 中的 __next__()
)方法中执行下一个逻辑步骤。根据逻辑,next()
方法内的代码最终可能看起来非常复杂并且容易出现错误。在这里,生成器提供了一个干净且简单的解决方案。
文中关于的知识介绍,希望对你的学习有所帮助!若是受益匪浅,那就动动鼠标收藏这篇《Python 的“yield”关键字有哪些应用?》文章吧,也可关注golang学习网公众号了解相关技术文章。
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
139 收藏
-
204 收藏
-
325 收藏
-
477 收藏
-
486 收藏
-
439 收藏
-
357 收藏
-
352 收藏
-
101 收藏
-
440 收藏
-
212 收藏
-
143 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习