python 生成器与迭代器(yield 用法)

背景

首先,我不会解释这两个名词,我看过很多遍解释,可还是看不懂,还是直接看使用情景吧。
我们以佩波拉契数列为例,当我们不知道迭代器的情况下,我们写出来的代码可能是这样子的:

'''这种方式计算fib(100)都很吃力'''
fib = lambda n:fib(n-2) + fib(n-1) if n>1 else 1

或者优化一下,变成这样子:

'''
将迭代结果存入temp中,避免重复的计算, 
这样可以提高计算速度,但是当计算fib(4000)时,会报:
“maximum recursion depth exceeded in comparison”
也就是说会超过最大迭代次数
'''
temp = {}
def fib(n):
    if n in temp:
        return temp[n]
    
    if n>1:
        _t = fib(n-1) +  fib(n-2)
        temp[n] = t
        return _t
    else:
        return 1

考虑到进一步的优化,我们就需要引进python的yield 语法

使用yield

yield的用法简单来说就 返回yield运行结果,当调用__next__时,再次运行至yield处,并返回 。所以我们可以利用这一特点,让fib函数每次只返回当前和前一个的值,再次运行时更新这两值, 并返回就好了。于是有了下面的代码:

'''
这次我们可以最大程度发挥计算机性能,
比上个版本能计算的更多也更快(我电脑能计算6位数的fib值)
'''
def fib_gen():
    '''
    这时一个佩波拉契数列的生成器,每次迭代返回当前值和前一个的值
    '''
    c , p = 1,1
    while True:
        yield p # 每次next运行到此,并返回p的值。当再次调用时,继续执行下面代码,
        p, c = c, c+p # 计算下次p的值
        
def fib(n):
    '''根据情况不断运行next(f),直到适合的条件'''
    f = fib_gen()
    for i in range(n+1):
        cur = next(f)
    return n

我们在看一个简单的生成器的例子

In [1]: array = [1,2,3,4]

In [2]: f = (i for i in array) # 是不是看起来很像“元组推倒式”,其实这是一个生成器。python并没有元组的推倒式

In [3]: f # 不信,我们来实际看看
Out[3]: <generator object <genexpr> at 0x10882df10>

In [4]: [i for i in f] # 生成器是可以迭代的,所以列表推到能取出值
Out[4]: [1, 2, 3, 4]

In [5]: [i for i in f]
Out[5]: [] # 此时,f中的数据全部取出,调用next(f) 取不出来值了

总结

  • 从这个例子中我们可以发现,当使用yield后,函数并不是立刻执行,而是调用next的时候,我们才取出值,没调用的时候,它只是一个生成器。相当于保存了上次运行的现场。
  • 当调用next时,没有找到yield返回时,便会终止,并报StopIteration错误, 本例子中是死循环,不会发生这种情况。感兴趣的可以把while True 改成while p < 100之类的试试
posted @ 2018-08-01 22:44  余震杰yzj  阅读(795)  评论(0编辑  收藏  举报