• python中的yield生成器详解


    #原创,转载请先联系

    在学习生成器之前,必须先了解一下迭代器。因为生成器就是一种特殊的迭代器,而且生成器用起来更加优雅。

    迭代器的详解可以参考我的另一篇博文:https://www.cnblogs.com/chichung/p/9537969.html

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

    # 列表生成式
    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

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

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

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

    方法1.我们可以创建一个迭代器类,然后把代码写进类里,用类来创建一个可迭代对象,然后用next()函数一个一个把结果迭代出来。

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

    口说无凭,下面我们2个方法都做一下,让你们体会一下:

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

    方法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。

  • 相关阅读:
    设置函数环境——setfenv(转)
    全局变量声明的规范化(转)
    利用__index和__newindex实现默认值表、监控表、只读表(转)
    php中的$_GET怎样获取带有井号“#”的參数
    Servlet配置load-on-startup
    LinQ—扩展方法
    CRT
    [C++] 获取IE代理server的账号password
    一步一步写算法(之hash表)
    android之PackageManager简单介绍
  • 原文地址:https://www.cnblogs.com/chichung/p/9543513.html
Copyright © 2020-2023  润新知