python之generator学习

动机

先看一个问题: 假设我们遇到一个需要长时间响应才能得到返回值的情境,比如深度学习中的大计算或者网络较长时间的响应等(这里我们用sleep()函数来代替),每次需要较长时间的计算才能得到结果.

1
2
3
4
5
6
7
from time import sleep
def long_time_compute():
rv = list()
for i in range(10):
sleep(2)
rv.append(i)
return rv

如上面所述,假如我想得到的最终结果含10个值,我需要20秒才能观察到最后结果,但是假如包含更长的值(如100呢)而且我想观察前几个值呢? 因为从前几个值就能发现一些有意义的东西,我不需要观察全部的值就能得到我想要的,怎么办?

明显要等到全部值计算完都返回是不符合逻辑的,如果我们只是观察结果不用到返回值的情况可以每次都print打印出每次的结果,但是这也不能解决所有的情况. 幸运的是我们可以通过generator来解决这种问题.

What is python Generator?

Introduced in Python 2.2 as an optional feature and finalized in version 2.3, [generators][1] are Python’s mechanism for [lazy evaluation][2] of a function that would otherwise return a space-prohibitive or computationally intensive list.

A Python generator is a function which returns a generator iterator (just an object we can iterate over) by calling yield. yield may be called with a value, in which case that value is treated as the “generated” value. The next timenext() is called on the generator iterator (i.e. in the next step in a for loop, for example),the generator resumes execution from where it called yield, not from the beginning of the function. All of the state, like the values of local variables, is recovered and the generator contiues to execute until the next call to yield.

generator可以不像一般的函数那样,一次返回一个值,下一次还在上次执行的地方继续运行程序返回下一个值,可以说是返回一系列值(One at a time).

Generator expressions

Introduced in Python 2.4, generator expressions are the lazy evaluation equivalent of list comprehensions.

不同于生成器,生成器表达式是list comprehensions的generator形式表达.与list comprehensions长得很像,但是返回一个generator而不是list.

此外,generator不能用下标来索引.

如下, 详细后面叙述.

1
2
3
4
5
6
7
>>> a = (x for x in range(8))
>>> a
<generator object <genexpr> at 0x104684960>
>>> a[2]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'generator' object has no attribute '__getitem__'

如何

对于开始提出的问题,如何使用generator来解决呢?

我们最后想要的是一个可以迭代的对象,大致为

1
2
x = iter(rv)
next_value = next(x)

我们知道python中许多方法和属性都是有对应的double underscore属性和方法的(如__add__).上面的对应为__iter__(python2中是next())和__next__方法.

于是我们可以使用一个类,实现上述两种方法来实现generator.

1
2
3
4
5
6
7
8
9
10
11
class ComputerLong:
def __iter__(self):
self.last = 0
return self
def __next__(self):
rv = self.last
self.last += 1
if self.last > 10:
raise StopIteration()
sleep(2)
return rv

但是这样读起来太费劲了,正常点的写法还是应该像上面函数那样. 更Generator的写法应该使用yield.

1
2
3
4
5
6
7
8
from time import sleep
def long_time_compute():
rv = list()
for i in range(10):
sleep(2)
yield i
# rv.append(i)
# return rv

Generator每次生成一个新的值时,采用yield关键字代替原来return的位置,相当于返回一个需要即可取的值.

Generator返回是一个可迭代的对象,因此可以使用for循环:

每次for循环中需要一个值时,便会调用generator对象的next()方法,取回一个值.

generator结束

当generator函数中使用return或者generator迭代到最后,将会返回一个StopIteration exception,当next到最后一个值时将会报错,因此generator只能够循环一次.

![][image-1]

最后:

  • generator可以用来返回一个迭代器,可以返回一个序列(每次一个值而不是直接全部的返回值).
  • yield可以看做next()方法return一个值并记住这个位置,下次在这个位置利用next()方法仍旧如此.
  • python2中generator next()方法为g.next(),而python3为g.__next__()

[1]: https://en.wikipedia.org/wiki/Generator_(computer_science)
[2]: https://en.wikipedia.org/wiki/Lazy_evaluation

[image-1]: /images/Screenshot2017-08-23_%E4%B8%8B%E5%8D%8810.27.24.png