• 生成器 yield


    由于生成器的其中一种创建方式与列表推导式很相似,这里先说一下列表推导式。

    列表推导式

    列表推导式又叫列表生成式,官方叫做 list comprehension。顾名思义,这个是用来生成列表的。

    用法:

    [x for x in iterable]
    

    一般情况下,可以用list()函数将序列转换成列表,比如:

    >>> list(range(1, 11))
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    

    上面的情况比较单一,如果复杂一点,比如要生成[1x1, 2x2, 3x3, ..., 10x10],这个时候就只能循环了,列表推导式就是用来简化这种情况的。

    >>> [x * x for x in range(1, 11)]
    [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
    

    把要生成的元素x * x放在前面,后面跟for循环,就可以把列表创建出来。运用列表推导式,可以写出非常简洁的代码。

    >>> L = ['Hello', 'World', 'IBM', 'Apple']
    >>> [s.lower() for s in L]
    ['hello', 'world', 'ibm', 'apple']
    
    生成器表达式

    通过列表推导式,可以直接生成一个列表,但是内存是有限的,如果一个列表太大,或者只需要访问列表中的部分元素,那其余大部分元素占用的空间就浪费了,生成器就是用来解决这个问题的。

    生成器在访问序列中的元素时,不是一下加载整个序列到内存,而是一边循环一边计算,也就是读取一个元素计算一次,这样如果序列很大就节省了大量空间。说白了,生成器就是一种特殊的迭代器。

    生成器有两种创建方法,第一种与列表推导式类似,只需要把列表推导式中的方括号[]改成圆括号()即可创建一个生成器(generator)。

    >>> L = [x * x for x in range(10)]
    >>> L
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    
    >>> g = (x * x for x in range(10))
    >>> g
    <generator object <genexpr> at 0x0000000001E74CA8>
    
    >>> for i in g:
    ... 	print(i)
    ... 
    0
    1
    4
    9
    16
    25
    36
    49
    64
    81
    

    创建生成器的第二种方法,是通过生成器函数。所谓生成器函数,就是在一个函数中包含yield语句。

    比如斐波那契函数:

    >>> def fib(max):
            n, a, b = 0, 0, 1
            while n < max:
                yield b
                a, b = b, a + b
                n = n + 1
            return 'done'
    ... ... ... ... ... ... ...
    

    带有 yield 的函数不再是一个普通函数,而是一个generator。

    >>> fib(10)
    <generator object fib at 0x0000000001DE6A40>
    
    >>> list(fib(10))
    [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
    

    需要注意的是,生成器函数和普通函数执行的流程是不一样的。普通函数是遇到 return 语句或者执行结束就返回。而生成器函数是遇到 yield 就返回一个迭代值,再次执行时从上次返回的 yield 语句处继续执行,看起来就像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前迭代值。

    好像说的有点绕,简单来说,yield就是中断函数并返回迭代值,而且中断不会对函数变量造成影响即状态不变,返回迭代值之后继续执行函数。

    再看看下面这个示例:

    >>> def odd():
            print('step 1')
            yield 1
            print('step 2')
            yield(3)
            print('step 3')
            yield(5)
    
    >>> odd()
    <generator object odd at 0x0000000001DE6A98>
    
    >>> for i in odd():
    ... 	print(i)
    ... 
    step 1
    1
    step 2
    3
    step 3
    5
    

    遇到yield就返回迭代值,这个返回类似return但不会退出函数(可以理解为挂起),然后继续执行函数,直到再次遇到yield。

    另外,上面生成器函数fib()最后的retrun的值没有返回(在普通函数中是可以返回的)。因为,调用生成器函数并没有执行函数代码,而是返回了一个generator object,然后再从generator object中迭代取值的时候才会真正执行函数代码,所以调用生成器函数的时候并没有报异常并退出,return的值也没有返回。

    在生成器中,无论迭代有没有完成,遇到return语句都会直接终止迭代并抛出StopIteration异常。。

    这里顺便说一下迭代器,迭代器可以通过内置函数next()获取迭代器中的下一个元素,迭代完成时会报StopIteration异常。

    >>> g = (x * x for x in range(5))
    >>> next(g)
    0
    >>> next(g)
    1
    >>> next(g)
    4
    >>> next(g)
    9
    >>> next(g)
    16
    >>> next(g)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration
    

    在 for 循环里,无需处理 StopIteration 异常,循环会正常结束。但是在while循环中会抛出异常,这时,需要捕获异常才会继续执行return语句,return的返回值包含在StopIteration的value中。

    >>> g=fib(6)
    >>> while True:
            x = next(g)
            print('g:', x)
    ... ... ... 
    g: 1
    g: 1
    g: 2
    g: 3
    g: 5
    g: 8
    Traceback (most recent call last):
      File "<stdin>", line 2, in <module>
    StopIteration: done
    

    再来看上面的fib()函数的StopIteration异常捕获:

    >>> g = fib(6)
    >>> while True:
            try:
                x = next(g)
                print('g:', x)
            except StopIteration as e:
                print('Generator return value:', e.value)
                break
    ... ... ... ... ... ... ... 
    g: 1
    g: 1
    g: 2
    g: 3
    g: 5
    g: 8
    Generator return value: done
    

    另一个示例,文件读取:

    如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。

    如果读取的文件很大,yield会分批返回,有效地避免了内存占用问题,轻松实现文件读取。

    >>> def read_file(fpath): 
            BLOCK_SIZE = 1024 
            with open(fpath, 'rb') as f: 
                while True: 
                    block = f.read(BLOCK_SIZE) 
                    if block: 
                        yield block 
                    else: 
                        return
    
    >>> g = read_file('users.txt')
    >>> for i in g:
    ... 	print(i)
    ... 
    b'keith1:18:110
    keith2:19:111
    keith3:20:112
    keith4:21:113'
    

    参考:
    https://docs.python.org/3/library/stdtypes.html#lists
    https://docs.python.org/3/reference/simple_stmts.html#the-yield-statement
    https://docs.python.org/3/reference/expressions.html#yieldexpr

  • 相关阅读:
    一个故事讲透供应链的三大战略
    电商系统之订单正向与逆向流程设计
    广告深度预估技术在美团到店场景下的突破与畅想
    美团智能客服核心技术与实践
    供应链里的批次管理,你了解多少
    对于HTML语义化的理解
    前端三大框架中Vue与React区别
    对于mvc模式的理解与学习
    对于javascript里面闭包的理解
    webpack的理解与使用
  • 原文地址:https://www.cnblogs.com/keithtt/p/7747548.html
Copyright © 2020-2023  润新知