如何理解python中yield关键词

“yield”关键词是python里面一个比较难理解的关键词,那么“yield”关键词是干啥用的?如何理解?首先我们从一个最简单的例子入手。

函数f使用了3个yield,参数分别是1、2、3,然后用一个循环去打印了所有的变量,但是再次迭代发现没有内容了。这里给我们几个直观的认识,一是函数里面的yield似乎是迭代一次执行一个yield,有点lazy+return的概念,二是yield只能被遍历一次。下面步入正题。


为了理解yield是干啥的,首先必须理解生成器。为了理解生成器,必须先理解可迭代对象(Iterables)。


什么是可迭代对象:当你创建了一个列表,你可以一个接一个地读取它里面的元素,一个接一个地读取它的元素我们就称之为迭代,例如:

>>> mylist = [1, 2, 3]
>>> for i in mylist:
...  print(i)
1
2
3

mylist就是一个可迭代对象。所有能用“for... in...”语法的对象都叫可迭代对象,例如list、string、文件等等。这些可迭代对象都比较灵活,你可以想怎么读就怎么读,因为你在内存中存储了它里面的所有元素。但是当你的元素很多很大时,你肯定不想把所有元素都放内存,生成器就出来了。


什么是生成器:生成器是迭代器,是一种只可以迭代一次的可迭代对象。生成器不把内容存在内存里,它们是在运行时计算出来的。例如:

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

语法基本相同除了这里是()而不是[],但是你不能第二次使用for循环去迭代它因为生成器只能被使用一次:上述迭代器生成了0,然后就忘了0,生成1忘了1,生成4忘了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!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

当你想要一个非常巨大的元素列表的时候,yield关键词应该是首选。

为了驾驭yield,你必须先理解当你调用一个函数的时候,函数体里的代码并没有真正的跑起来。这个函数仅仅是返回了一个生成器对象!当你用for循环去遍历你的生成器时,你的代码才会被依次一个接一个继续执行。当你的for第一次调用函数返回的生成器对象时,它会执行函数体知道碰到第一个yield,然后返回第一个值。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 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 candidate's 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

上述代码能够work是因为python并不关心函数的参数是不是列表,python只需要一个可迭代对象,可以是字符串、列表、元组或者生成器。这就是动态类型中的鸭子类型,也是python为什么这么cool的原因。这里还有一段代码方便理解生成器:

>>> 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 # The 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模块包含了很多去操作可迭代对象的函数,包括复制生成器、连接生成器、组合两个列表等,一行代码。使用的时候只要import itertools即可。这里有个例子,打印1、2、3、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)]

好了就到这,更多可以参考链接:https://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do

4 views

©2020 by EasyCSTech. Special thanks to IPinfo​ and EasyNote.