python中的yield关键字

Yield

基础概念

可迭代对象

python中,一般能够被for循环遍历的对象就是可迭代对象。
拥有__iter__()方法的对象称之为可迭代对象,__iter__()方法返回一个迭代器。

迭代器

迭代器是访问集合内元素的一种方式。迭代器对象从集合的第一个元素开始访问,直到所有的元素都被访问一遍后结束。
可以使用工厂函数iter()返回一个迭代器。

>>> iter([1,2,3])
<listiterator object at 0x1100b6a50>

for循环遍历可迭代对象过程

  1. Python将对关键字in后的对象调用iter函数获取迭代器
  2. 调用迭代器的next方法获取元素,直到抛出StopIteration异常。
  3. 对迭代器调用iter函数时将返回迭代器自身,所以迭代器也可以用于for语句中,不需要特殊处理。
    代码如下
it=iter(lst)
try:
      while True:
          val=it.next()
          print val
except
      StopIteration:
          pass

正文

在stackoverflow中见到这样一个问题 What does the "yield" keyword do in python

其中排名最高的回答对我有很大帮助,因此将其翻译下来分享给大家答案
一下是译文:

要理解什么是yield关键字,必须要理解什么是生成器,而要理解生成器,首先要理解什么是迭代器

迭代器

当你生成了一个list,可以一个接一个地访问这个list中的元素,这种行为被称为迭代。

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

上面代码中的mylist就是一个迭代器。list类型是可迭代的。

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

在python中可以通过"for... in ..."这种方式遍历的都是迭代器,像lists,strings,files...

迭代器是很好用的,因为你可以很方便地遍历其中的元素。但是这些数据都是存在内存里的,当数据量非常大时,这种方式就不是非常理想了。

生成器

生成器是迭代器的一种,但是只能被迭代一次。这是因为生成器并不会将所有的数据存在内存里,而是在用到的时候生成数据。

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

上面用[]生成了一个迭代器,而这里用()生成了一个生成器。
但是再执行

for i in mygenerator:
    print(i)

不会有任何输出,因为生成器只能使用一次。之前一次遍历中,生成器计算的到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关键词就很有用了。
要理解yield,你必须了解当返回生成器的函数被调用时,里面的代码实际上并没有运行。这个函数只是返回了一个生成器的对象。这有点让人难以理解。

在for循环中,这个生成器才会被使用到。

现在进入困难的部分:
for循环中,生成器第一次被调用到时,返回这个生成器的函数会顺序执行到yield部分,然后返回这个循环的第一个值。每次调用到生成器,都会执行函数中的循环一次,然后返回下一个值,直到没有值被返回。

当函数不再执行到yield的时候,生成器为空。这可能是循环结束了或者不再满足"if/else"判断。

回答题主的问题

生成器

# Here you create the method of the node object that will return the generator
def node._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)
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 distance is ok, then you can fill the 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.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

上面的代码有几个有意思的地方

  • 循环迭代了一个list的同时,也在往list里面添加元素。这种方式可以很简洁遍历所有相邻的数据,虽然有可能造成无限循环。
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

上面的代码会返回所有的生成器,但是while不断得产生新的生成器。

  • extend()方法是list的一个方法,传入一个迭代器,然后将其加入到list中

我们一般这样用extend

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

在上面的代码中,传入了一个生成器,这样做有两个好处

  1. 不需要读两次数据
  2. 字节点不用都存在内存中

上面的代码是可行的,因为python并不关心传入的参数是否是一个list。它关心传入的是不是一个迭代器,所以strings,lists,tuples,generators都是可以作为参数传入的!这叫做鸭子类型,也是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
...

生成器可以做很多事情,上面代码展示了如何利用yield控制资源的访问

Itertools-最好的朋友

itertools模块中有很多控制生成器的方法。

看下面的例子,看看四匹马赛跑可能的顺序组合

>>> 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()方法

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容