python中的yield生成器详解

#原创,转载请先联系html

在学习生成器以前,必须先了解一下迭代器。由于生成器就是一种特殊的迭代器,并且生成器用起来更加优雅。函数

迭代器的详解能够参考个人另外一篇博文:http://www.javashuo.com/article/p-sxtfliuk-do.html学习

先说一种比较简单的生成器,经过例子慢慢来体会什么是生成器。spa

# 列表生成式
L = [x for x in range(5)]
print(L)

#简单的生成器
G = (x for x in range(5))  # G就是一个生成器,也是一个迭代器,迭代器也是可迭代对象,因此这个G也能够说是可迭代对象
print(next(G))
print(next(G))
print(next(G))
print(next(G))
print(next(G))

输出:
[0, 1, 2, 3, 4]
0
1
2
3
4

把列表生成器的[]改成()就变成一个简单的生成器。由上面的例子,咱们大概能够知道,生成器就是一个迭代器,把数据一个一个拿出来,能够减小内存的负担。code

那么,yield又是一个什么东西呢?为何说他优雅呢?htm

当咱们写的代码输出的结果,想一个一个出来。有两种经常使用的方法:对象

方法1.咱们能够建立一个迭代器类,而后把代码写进类里,用类来建立一个可迭代对象,而后用next()函数一个一个把结果迭代出来。blog

方法2.咱们能够用代码函数的合适位置加上yield,这时候这个函数就变成一个生成器了,不须要再建立一个迭代器类,不须要再写__iter__,__next__方法了。这样一来不是很方便,很优雅吗?哈哈哈哈~内存

口说无凭,下面咱们2个方法都作一下,让大家体会一下:get

咱们作一个斐波那契的数列生成器。斐波那契数列的第一个数是0,第二个数是1,第三个数是第1、二个数相加,第四个数是第2、三个数相加......

方法1:

class FeiboIterator():
    def __init__(self):
        self.a = 0
        self.b = 1

    def __iter__(self):
        return self

    def __next__(self):
            num = self.a
            self.a,self.b = self.b,self.a+self.b
            return num


iterator = FeiboIterator()
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))

输出:
0
1 2 3 5 8 13

是否是很麻烦?又要初始化,又要写__iter__和__next__魔方方法。

方法2:

def feibo():
    a = 0
    b = 1
    while True:
        yield a  # 假如yield后面紧接着一个数据,就会把数据返回,做为next()函数或者for ...in...迭代出的下一个值
        a,b = b,a+b


generator = feibo()

print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator))

输出:
0
1
1
2
3
5
8
13

看!只有6行代码,是否是很elegant?关于这个程序是怎么运行的?yield是怎么运做的?咱们等下就讲,如今须要注意几点:

1.上面代码的红色字那里!假如yield后面紧接着一个数据,就会把数据返回,做为next()函数或者for ...in...迭代出的下一个值。

2.假如函数中有yield,就再也不是函数。而是一个能返回生成器的函数!注意!是返回,这个函数并非一个生成器。(修正:这句话发现有错误,这个函数也是一个生成器)

3.拿到函数的生成器后,能够和迭代器同样,用next()函数得到下一个值。

好了,该来理解一下yield是怎么运做的了!

1.第一次唤醒生成器时,是从函数的起始位置开始,直到遇到yield,就会暂停函数,挂起函数。
2.第二次唤醒生成器时,是从yield断点处开始,直到又遇到yield。
3.当生成器已经没有yield,再使用next,则抛StopIteration异常。

而后,咱们来理一下上面用yield写的代码。

第一次用next()唤醒生成器时,从函数的开头开始运行,遇到yield a,返回a,而后暂停函数,并记住函数是运行到这个位置的。

第二次唤醒生成器,从yield a断点处开始,而后a,b开始赋值,while True循环又碰见yield a,返回a,而后暂停函数,并记住函数是运行到这个位置的。

下面唤醒多少次都是这个道理,可是因为这个函数是死循环,因此不会没有yield,也就不会抛出StopIteration异常。

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

其实yield还能接受值,用send方法进行传入。代码体会一下:

def gg():
    i = 1
    while True:
        recv = yield i
        print("接收到一个值:",recv)
        i += 1

generator = gg()

print(next(generator))
print(generator.send("456"))
print(generator.send("789"))

输出:
1
接收到一个值: 456
2
接收到一个值: 789
3

实现过程和上面的例子同样。

要懂得的是,yield = a,会返回a。

b = yield,会把yield接收的值赋值给b。

相关文章
相关标签/搜索