python中yield语句的作用_python中yield语句详解——译自stack overflow

翻译水平一般…请见谅…

原作者问题:

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 方法被调用时,发生了什么?一个列表被返回了吗?还是一个元素被返回了?它又被再次调用了吗?接下来的调用什么时候停止?

下面是得票最多的答案:来自e-satis

为了理解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…”结构来进行操作的对象都是可迭代的:列表,字符串,文件……

这些迭代对象用起来十分顺手因为你可以按你想的那样读取它们,但是你需要将所有元素的值都存储在内存中,如果你有很多数据,显然你并不会愿意这么做。

生成器

生成器属于迭代器,但你只可以在生成器上迭代一次。因为生成器把所有的值存储在内存中,它们动态地生成这些值。

>>> 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类似,除了函数返回一个生成器。

>>> def createGenerator():

... mylist = range(3)

... for i in mylist:

... yield i*i

...

>>> mygenerator = createGenerator() # 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条件。

解释下你的代码:

生成器:

# 这里你创建了一个node对象的方法,它会返回生成器。

def node._get_child_candidates(self, distance, min_dist, max_dist):

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

# 如果在node对象左边仍然有一个child

# 并且 如果distance满足条件, 返回下一个child

if self._leftchild and distance - max_dist < self._median:

yield self._leftchild

# 如果在node对象右边仍然有一个child

# 并且 如果distance满足条件, 返回下一个child

if self._rightchild and distance + max_dist >= self._median:

yield self._rightchild

# 当函数执行到这里,生成器可以被认为是空的

# 生成器中不再有两个值:left child和right child

调用:

# 创建一个空列表和一个包含当前对象引用的列表

result, candidates = list(), [self]

# 对candidates的循环 (一开始他们只包含一个元素)

while candidates:

# 获得最后一个candidate并把它从列表中移除

node = candidates.pop()

# 获得obj和candidate之间的distance

distance = node._get_dist(obj)

# 如果distance满足条件, 你就可以拓展result

if distance <= max_dist and distance >= min_dist:

result.extend(node._values)

# 在candidates中添加candidate的children

# 所以循环将一直执行直到其遍历了candidate的children的children…

candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

return result

这段代码包含几个巧妙的部分:

循环在列表上迭代但同时列表又在扩展自身,这是种比较简洁的方法来遍历所有的嵌套数据,虽然其有一定的危险性因为你可能使用一个无限循环。在你的代码中,candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))囊括了生成器的所有的值,但是while语句一直在创建新的生成器对象而这些生成器对象会产生不同的值。

extend()方法是列表对象的方法,其需要一个可迭代对象作为参数并把这个可迭代对象的值加入列表。

一般我们向它传递一个列表:

>>>a = [1, 2]

>>>b = [3, 4]

>>>a.extend(b)

>>>print(a)

[1, 2, 3, 4]

但在你的代码里这个方法获得了一个生成器,这很好因为:

1、你不需要读取这些值两次。

2、可能会有很多children对象但你不想把他们都存放在内存中。

它能工作是因为python不关心extend方法的参数是否是一个列表,python期望这个方法的参数是可迭代对象所以字符串、列表、元组和生成器都可以!这叫做填鸭式输入(duck typing),这也是python如此cool的一个原因。但这又是另一个话题了……

你可以就此打住了,也可以再读一点来看看生成器的一个高级用法:

控制生成器的输出

>>> class Bank(): # 我们来建立一家银行并搭一些ATM机

... crisis = False

... def create_atm(self):

... while not self.crisis:

... yield "$100"

>>> hsbc = Bank() # 一切正常时,你想要多少,ATM机就给你多少

>>> 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 # 金融危机来了,没钱了!

>>> print(corner_street_atm.next())

>>> wall_street_atm = hsbc.create_atm() # 甚至对新的ATM机来说也是这样

>>> print(wall_street_atm.next())

>>> hsbc.crisis = False # 问题是,即使危机过去了,ATM机还是空的

>>> print(corner_street_atm.next())

>>> brand_new_atm = hsbc.create_atm() # 重新搭一个来投入商业运营

>>> for cash in brand_new_atm:

... print cash

$100

$100

$100

$100

$100

$100

$100

$100

$100

...

这对很多东西来说都很有用例如控制某个资源的入口。

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__()方法)。可迭代对象是你可以从中获得迭代器的任何对象。迭代器是让你可以在可迭代对象上迭代的对象。

更多关于迭代的信息可以参考这篇文章《for循环如何工作》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值