`
wx1568037608
  • 浏览: 33435 次
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

[翻译]Python中yield的解释

 
阅读更多

问题:

Python中yield关键字的作用是什么?它做了什么?

例如,我想理解以下代码

def node._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 = list(), [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这个函数被调用时发生了什么?返回了一个列表?还是只返回了一个元素?然后又再次被调用?什么时候调用结束?

这段代码的来源 Jochen Schulz (jrschulz), who made a great Python library for metric spaces. 完整源码链接: here


要了解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…” 处理的都是可迭代对象:列表,字符串,文件…. 这些迭代对象非常便捷,因为你可以尽可能多地获取你想要的东西

但,当你有大量数据并把所有值放到内存时,这种处理方式可能不总是你想要的 (but you store all the values in memory and it’s not always what you want when you have a lot of values.)

生成器

生成器是迭代器,但你只能遍历它一次(iterate over them once) 因为生成器并没有将所有值放入内存中,而是实时地生成这些值

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...    print(i)
0
1
4

这和使用列表解析地唯一区别在于使用()替代了原来的[]

注意,你不能执行for i in mygenerator第二次,因为每个生成器只能被使用一次: 计算0,并不保留结果和状态,接着计算1,然后计算4,逐一生成

yield

yield是一个关键词,类似return, 不同之处在于,yield返回的是一个生成器

>>> def createGenerator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

这个例子并没有什么实际作用,仅说明当你知道你的函数将产生大量仅被读取一次的数据时,使用生成器将是十分有效的做法

要掌握yield,你必须明白 - 当你调用这个函数,函数中你书写的代码并没有执行。这个函数仅仅返回一个生成器对象

这有些狡猾 :-)

然后,在每次for循环使用生成器时,都会执行你的代码

然后,是比较困难的部分:

第一次函数将会从头运行,直到遇到yield,然后将返回循环的首个值. 然后,每次调用,都会执行函数中的循环一次,返回下一个值,直到没有值可以返回

当循环结束,或者不满足”if/else”条件,导致函数运行但不命中yield关键字,此时生成器被认为是空的

问题代码的解释

生成器:

# 这你你创建了node的能返回生成器的函数
def node._get_child_candidates(self, distance, min_dist, max_dist):

# 这里的代码你每次使用生成器对象都会调用

# 如果node节点存在左子节点,且距离没问题,返回该节点
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 = list(), [self]

# 当前候选非空,循环(开始时仅有一个元素)
while candidates:

    # 从候选列表取出最后一个元素作为当前节点
    node = candidates.pop()

    # 获取obj和当前节点距离
    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))
    # 注意这里extend会反复调用获取到所有生成器返回值

return result

这段代码包含几个灵活的部分:

1.这个循环遍读取历候选列表,但过程中,候选列表不断扩展:-)

这是一种遍历嵌套数据的简明方法,虽然有些危险,你或许会陷入死循环中

在这个例子中, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) 读取了生成器产生的所有值, 同时while循环产生新的生成器对象加入到列表,因为每个对象作用在不同节点上,所以每个生成器都将生成不同的值

2.列表方法extend() 接收一个生成器,生成器的所有值被添加到列表中

通常,我们传一个列表作为参数:

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

但是,在代码中,这个函数接受到一个生成器

这样的做法好处是:

1.你不需要重复读这些值

2.你可能有海量的子节点,但是不希望将所有节点放入内存

并且,可以这么传递生成器作为参数的原因是,Python不关心参数是一个方法还是一个列表

Python接收可迭代对象,对于字符串,列表,元组还有生成器,都适用!

这就是所谓的“鸭子类型”(duck typing), 这也是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())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # it's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> 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
...

这在很多场景都非常有用,例如控制资源的获取

Itertools

一个很好的工具

itertools模块包含很多处理可迭代对象的具体方法. 例如

复制一个生成器?连接两个生成器?一行将嵌套列表中值分组?不使用另一个列表进行Map/Zip? (Ever wish to duplicate a generator? Chain two generators? Group values in a nested list with a one liner? Map / Zip without creating another list?)

只需要使用itertools模块

一个例子,4匹马赛跑的可能抵达顺序

>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> 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()方法)

你可以获取一个迭代器的任何对象都是可迭代对象,迭代器可以让你迭代遍历一个可迭代对象(Iterators are objects that let you iterate on iterables.) [好拗口:]

更多关于这个问题的 how does the for loop work

如果你喜欢这个回答,你也许会喜欢我关于 decorators 和 metaclasses 的解释

分享到:
评论

相关推荐

    《StackOverFlow-Python翻译》-深入理解python,超级推荐

    《StackOverflow-Python翻译》是一本深度探讨Python编程...以上只是《StackOverflow-Python翻译》中部分涵盖的主题,通过这本书,读者将能更深入地了解Python,提升自己的编程水平,从而编写出更高效、更优雅的代码。

    Python yield使用方法示例

    1. iterator叠代器最简单例子应该是数组下标了,且看下面的c++代码: ...上面的代码翻译成python:  复制代码 代码如下: array = [i for i in range(10)]for i in array: print i,  首先,array

    Python 中文 API v2.4(HTML).rar

    例如,`with`语句的引入使得资源管理更加方便,`yield`关键字的扩展支持了生成器表达式,这些都是Python 2.4中的重要变化。 Python中文手册包含以下几个主要部分: 1. **语言参考**:这部分详细介绍了Python的语法...

    Python3.7.2中文文档-6.Python的HOWTO文档

    这部分将解释Unicode的概念,以及在Python中处理Unicode字符串的方法。 10. **单元测试**: Python的unittest模块提供了一套完整的单元测试框架。这部分会介绍如何编写和执行单元测试,以及断言、测试套件和测试...

    Python3.7.2中文文档-7.Python常见问题

    Python3.7.2中文文档中的“7.Python常见问题”章节涵盖了Python编程过程中经常会遇到的问题和解决方案。这部分内容旨在帮助开发者解决他们在编程实践中可能遇到的困惑,提高编程效率。以下是一些重要的知识点: 1. ...

    Python3.7.2中文文档-词汇表

    在Python3.7.2的官方中文文档中,词汇表部分对Python编程语言的众多术语和概念进行了详尽的解释。本篇内容将对词汇表中提到的主要知识点进行扩展说明,以帮助读者更全面地理解这些概念。 **2to3工具** 2to3工具是...

    Python官方教程中文版3.4.1-带标签

    带标签.pdf作为官方文档的中文翻译,将详细介绍这些概念和用法,并通过实例演示如何在实际开发中应用。配合readme.txt文件,读者可以更好地了解教程的结构和使用方法,从而系统地学习Python 3.4.1的各个方面。 总的...

    Python3.2.3官方文档(中文版)高清完整版

    这份"Python3.2.3官方文档(中文版)高清完整版"是中文社区对官方文档的翻译,旨在帮助中文用户更好地理解和使用这一版本的Python。 Python 3.2.3的文档详细介绍了该语言的核心概念、语法结构以及标准库,是学习和...

    Python进阶

    《Python进阶》是一本以英文原版书籍《Intermediate Python》为...整体来看,《Python进阶》一书以独特的视角和清晰的解释,向读者展示了Python这门语言的独特魅力,并在实际开发中如何有效地使用这些高级特性和工具。

    Python3.0 Tutorial 简体中文版

    这个教程由译者刘鑫、尹伟铭和Kernel1983共同翻译,旨在帮助中文用户更方便地学习 Python 3.0 的核心概念和编程技巧。HTML 制作由刘鑫负责,确保了教程的可读性和互动性。 **1. Python 3.0 的新特性** - **打印...

    深入 Python 3 (Dive Into Python 3)

    《深入 Python 3》是 Python 编程领域的一本经典教程,由 Mark Pilgrim 编著,woodpecker 社区进行了中文翻译。这本书旨在帮助读者深入理解 Python 3 的核心概念、语法特性以及标准库的使用。通过阅读本书,无论是...

    Python3.2.3官方文档(中文版)

    3. **Cython或PyPy**:通过编译Python代码为C代码或使用PyPy解释器,可以显著提高Python程序的运行速度。 七、其他特性 1. **上下文管理协议**:`__enter__`和`__exit__`方法实现资源的自动管理,常用于文件操作。...

    经典Python面试题之Python基础篇.docx

    - **解释型语言**: 源代码在执行时逐行被解释器翻译成机器码并立即执行(如 Python)。 - **编译型语言**: 先将源代码编译成机器码后存储,运行时直接执行机器码(如 C++)。 #### 5. Python解释器种类及其特点? ...

    Python 2.4 Quick Reference Card

    - **概述**: Python 解释器接受多种命令行参数来控制其运行方式。 - **常用选项**: - `-c`: 运行单行命令或标准输入中的命令。 - `-m`: 运行库中的模块作为脚本。 - `-v`: 增加详细输出(调试信息)。 - `-O`: ...

    Papython:Estudos Python em andamento!

    "是葡萄牙语,翻译成中文就是"正在进行的Python学习",意味着这是一个持续更新的学习过程,内容可能涵盖基础到进阶的Python知识。 描述中的"番红花"可能是指项目或文件夹的一个命名,与实际的Python学习内容关系...

    中文分词:采用二元词图以及viterbi算法.docx

    中文分词作为自然语言处理(NLP)领域的重要基础技术之一,在信息检索、文本挖掘、机器翻译等多个方面都有着广泛的应用。不同于英语等以空格明确分割单词的语言,中文是以连续字符组成的文本流,因此需要通过特定的...

    2021-2022计算机二级等级考试试题及答案No.3973.docx

    - **知识点**: Python中的异常处理。 - 正确答案:**C** - 解析:在这个示例中,由于输入的是字符串而不是整数,因此会触发异常处理,输出`请输入整数`。 #### 23. 命令行中执行程序 - **知识点**: 命令行中执行...

    【ASP.NET编程知识】.NET Core3.1发布(翻译).docx

    F# 4.7也带来了许多改进,如隐式yield表达式和对LangVersion的支持。 .NET Standard 2.1的引入扩大了可在.NET Core和Xamarin中使用的类型集合,使得跨平台开发更加便利。同时,.NET Core 3.1支持Windows Forms和WPF...

    LuaBind 源码 (Lua增强库)

    resume_fucntion()调用的返回值是 lua_yield()的第一个传入参数.当你想要继续一个 协程的时候,你只需要调用 resume() 在你的 lua_State() 上,因为它已经在执行一个函数 (即先前出入的入口函数),所以你不需要再次传入...

Global site tag (gtag.js) - Google Analytics