zoukankan      html  css  js  c++  java
  • 生成器 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

  • 相关阅读:
    几个新角色:数据科学家、数据分析师、数据(算法)工程师
    人类投资经理再也无法击败电脑的时代终将到来了...
    Action Results in Web API 2
    Multiple actions were found that match the request in Web Api
    Routing in ASP.NET Web API
    how to create an asp.net web api project in visual studio 2017
    网站漏洞扫描工具
    How does asp.net web api work?
    asp.net web api history and how does it work?
    What is the difference between a web API and a web service?
  • 原文地址:https://www.cnblogs.com/keithtt/p/7747548.html
Copyright © 2011-2022 走看看