python function if yield_Python中的yield关键字

Python中的yield关键字

这是stackoverflow上一个关于yield关键字的问题以及它被推荐次数最高的一个答案

问题:

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

例如,下面的这份代码:

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方法被调用时,发生了什么?是一个list对象被返回了?还是一个单独的元素?或者是它再次被调用了?这个调用什么时候会结束呢?

答案

在理解yield关键字之前,需要先理什么是迭代器(iterables)

迭代器(Iterables)

当创建一个列表后,你就可以一个接着一个地读取它的元素, 这种操作就叫做迭代(iteration):

>>> 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、string、file等等

由于可以随意地读取这些对象中的元素,这些可迭代对象使用起来非常方便,但是这样做会把可迭代对象所有的值都存在内存中,当有很多个元素的时候,这样做可能并不合适。

生成器(Generators)

生成器是可以迭代的,但它是一个只可以读取一次的迭代器。生成器并不会把所有的值都存在内存中,而是实时地生成数据

>>> mygenerator = (x*x for x in range(3))

>>> for i in mygenerator:

... print(i)

0

1

4

除了把[]换成()之外,生成器看起来和列表推导式没有什么不同。但是,调用之后,你不能再次使用for i in mygenerator,因为生成器只可以被迭代一次:它计算出0,然后计算出1,同时丢弃掉0,然后最终计算出4,丢弃掉1,一个接着一个。

Yield 关键字

yield是一个类似与return的关键字,只是这个函数会返回的是一个生成器(Generator)

>>> 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

这个例子并没有什么用处,但是,我们可以看出,createGenerator函数会返回一大批你只需要读取一次的值。

为了理解yield,你必须理解: 当你调用这个函数时,你写在函数体内部的代码并没有运行。这个函数只是返回了一个生成器对象(这可能有一点tricky)。

然后,每次当你使用for进行迭代的时候,你的代码才会执行。

接下来是关键的部分:

使用for第一次调用从你的函数中创建出的生成器对象时,将会执行从函数起始位置到yield所在位置的代码并返回这个循环(函数内部定义)的第一个值。随后的每一次调用(for)都会继续执行你的函数内部的下一轮循环,并返回下一个值,直到没有值可以返回为止。

原文:

The first time the for calls the generator object created from your function, it will run the code in your function from the beginning until it hits yield, then it'll return the first value of the loop. Then, each other call will run the loop you have written in the function one more time, and return the next value, until there is no value to return.

如果生成器内部没有yield关键字,那么这个生成器将会被认为是空的。这可能是因为循环结束了,或者是没有满足if/else判断条件

代码解析

生成器

# Here you create the method of the node object that will return the generator

# 在node对象中创建一个返回生成器的方法

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 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 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 is 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)

# 在candidates中循环(在初始时值含有一个元素。根节点)

while candidates:

# Get the last candidate and remove it from the list

# 获取最近的一个candidate并将它从列表中移除

node = candidates.pop()

# Get the distance between obj and the candidate

# 获取obj和candidate的距离

distance = node._get_dist(obj)

# If distance is ok, then you can fill the result

# 如果距离符合要求,填入result中

if distance <= max_dist and distance >= min_dist:

result.extend(node._values)

# Add the children of the candidate in the candidates list

# so the loop will keep running until it will have looked

# at all the children of the children of the children, etc. of the candidate

# 将candidates列表中candidate的子节点添加到candidates列表中

# 以确保遍历了candidate所有的子节点以及子节点的子节点。。。

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

return result

这份代码包含了几个巧妙的地方:

我们对一个列表进行迭代的同时,列表还在不断地扩展。尽管这样做有可能导致无限迭代,但是它是一个简单的遍历所有数据的方式。在这个case中,candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))穷尽了生成器的所有值,但是while循环一直在创建新的生成器对象,由于传入的节点不同,这些生成器对象会产生不同的值。

extend() 是一个迭代器方法,作用于迭代器本身并把值添加到列表中

通常,我们会传一个list参数

>>> a = [1, 2]

>>> b = [3, 4]

>>> a.extend(b)

>>> print(a)

[1, 2, 3, 4]

但是,上面的代码是一个生成器,这样做很巧妙,因为:

你不需要读取每个值两次

你可以有很多子对象,但是不必将他们都存储在内存里面

这份代码是可以运行的,因为Python并不关心方法的参数是否是一个list对象,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())

>>> 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

...

这能被用来做资源访问权限控制

Itertools,你最好的朋友

itertools模块包含了很多特殊的迭代方法,你是否曾经想过复制一个迭代器?链接两个迭代器?将嵌套的列表分组?执行不创建新列表的zip/map操作?

所有的这些,只需要import itertools

来一个例子?让我们看看比赛中4匹马到达终点的先后顺序的所有可能情况:

>>> 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__() 方法)的过程。可迭代对象是通过它可以获取到一个迭代器的任一对象。迭代器是那些允许你迭代可迭代对象的对象。

更多细节可以阅读 http://effbot.org/zone/python-for-statement.htm

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值