zoukankan      html  css  js  c++  java
  • 生成器(generator) 详解

    1. 生成器是什么?

    利用迭代器,我们可以在每次迭代获取数据(通过next()方法)时按照特定的规律进行生成。但是我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合next()函数进行迭代使用,我们可以采用更简便的语法,即生成器(generator)。生成器是一类特殊的迭代器。
     
     
    2.列表生成式
     
    2.1列表推导式书写形式:  
    [表达式 for 变量 in 列表]    或者  [表达式 for 变量 in 列表 if 条件]
    示例代码如下:
    >>>[i for i in range(10)]
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    示例代码如下:
    >>>[x**2 for x in [1,2,5,8,7] if x>5]
    [64, 49]
     
    2.2 列表生成式:
    列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。
    列表生成式书写形式:  
     (表达式 for 变量 in 列表)   或者  (表达式 for 变量 in 列表 if 条件)
    列表生成式不同于列表推导式, 创建列表推导式和列表生成式的区别仅在于最外层的”[]“和"()",列表推导式是一个list,而列表生成式是一个generator。切不可相混淆。
     

    3. 创建生成器方法1

    生成器表达式:要创建一个生成器,有很多种方法。第一种方法很简单,即生成器表达式,只要把一个列表推导式的 [ ] 改成 ( );
    如下代码返回的不是一个列表,而是一个生成器对象, 可通过 next() 函数 或者__next__() 方法获得generator的下一个返回值:
    >>>obj = (i for i in range(5))
    >>>obj
    <generator object <genexpr> at 0x03F79D80>
    >>>next(obj)
    0
    >>>next(obj)
    1
    >>>obj.__next__()
    2
    >>>obj.__next__()
    3
    >>>obj.__next__()
    4

    注意:generator保存的是算法,每次调用next()方法,就计算出generator的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。因为generator也是可迭代对象,所以可以使用for循环来取出数据。

    >>> list = (x *2 for x in range(5))
    >>> for num in list:
    ...          print(num)
    ...
    0
    2
    4
    6
    8

    4. 创建生成器方法2

    生成器函数: 在函数中如果出现了yield关键字,那么该函数就不再是普通函数,而是生成器函数。

    但是生成器函数可以生产一个无限的序列,这样列表根本没有办法进行处理。yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator。generator非常强大。如果推算的算法比较复杂,用类似列表生成式的 for 循环无法实现的时候,还可以用函数来实现。

    In [30]: def fib(n):
      ....:   current = 0
      ....:   num1, num2 = 0, 1
      ....:   while current < n:
      ....:     num = num1
      ....:     nm1, num2 = num2, num1+num2
      ....:     current += 1
      ....:     yield num
      ....:     return 'done' ....: In [31]: F = fib(5) In [32]: next(F) Out[32]: 0 In [33]: next(F) Out[33]: 1 In [34]: next(F) Out[34]: 1 In [35]: next(F) Out[35]: 2 In [36]: next(F) Out[36]: 3 In [37]: next(F) --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-37-8c2b02b436bn> in <module>() ----> 1 next(F) StopIteration: done
     
    在使用生成器实现的方式中,我们将原本在迭代器__next__方法中实现的基本逻辑放到一个函数中来实现,但是将每次迭代返回数值的return换成了yield,此时新定义的函数便不再是函数,而是一个生成器了。
    简单来说:只要在def中有yield关键字的 就称为 生成器.
    在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield num 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield num 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。

     

    5. yield 与 return

    5.1 在一个生成器中,如果没有return,则默认执行到函数完毕时返回StopIteration;

    >>> def g1():
    ...   yield 6 
    ... 
    >>> g=g1() 
    >>> next(g)  #第一次调用next(g)时,会在执行完yield语句后挂起,所以此时程序并没有执行结束。 
    6
    >>> next(g)  #程序试图从yield语句的下一条语句开始执行,发现已经到了结尾,所以抛出StopIteration异常。 
    Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
    StopIteration 
    >>>

    5.2 如果在return后返回一个值,那么这个值为StopIteration异常的说明,不是程序的返回值。

    >>> def g2():
    ...   yield 'hello' 
    ...   return 'error information' 
    ... 
    >>> g=g2()
    >>> next(g) 
    'hello' >>> next(g) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration: error information

    5.3 如果遇到return,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。

    >>> def g3():
    ...   yield 'a' 
    ...   return 
    ...   yield 'b' 
    ... 
    >>> g=g3() 
    >>> next(g) #程序停留在执行完yield 'a'语句后的位置。 
    'a'
    >>> next(g) #执行到此处,程序发现下一条语句是return,所以抛出StopIteration异常,这样yield 'b'语句永远也不会执行。 
    Traceback (most recent call last):
        File "<stdin>", line 1, in <module> 
    StopIteration

    5.4 close()函数   手动关闭生成器函数,后面的调用会直接返回StopIteration异常。

    >>> def g4():
    ...  yield 1
    ...  yield 2 
    ...  yield 3 
    ...
    >>> g=g4() 
    >>> next(g) 
    1 
    >>> g.close() 
    >>> next(g) #关闭后,yield2 和yield3 语句将不再起作用 
    Traceback (most recent call last):
        File "<stdin>", line 1, in <module> 
    StopIteration
     
    5.5 send()函数 
    我们除了可以使用next()函数来唤醒生成器继续执行外,还可以使用send()函数来唤醒执行。使用send()函数的一个好处是可以在唤醒的同时向断点处传入一个附加数据。生成器函数最大的特点是可以接受外部传入的一个变量,并根据变量内容计算结果后返回。
    In [35]: def gen():
    ....:     i = 0
    ....:     while i<5:
    ....:       temp = yield i
    ....:       print(temp)
    ....:       i+=1
    ....:
    
    In [43]: f = gen()
    
    In [44]: next(f)
    Out[44]: 0
    
    In [45]: f.send('haha')
    haha
    Out[45]: 1
    
    In [46]: next(f)
    None
    Out[46]: 2
    
    In [47]: f.send('haha')
    haha
    Out[47]: 3

    上方代码执行到yield时,gen函数作用暂时保存,返回 i 的值; temp接收下次f.send("haha"),即send发送过来的值,next(f)等价f.send(None)

    总结

    • 使用了yield关键字的函数不再是函数,而是生成器。(使用了yield的函数就是生成器)
    • 可作用于for循环的对象都是Iterable类型;
    • next(f) 等价于 f.send(None)
    • yield关键字有两点作用:
      • 保存当前运行状态(断点),然后暂停执行,即将生成器(函数)挂起
      • 将yield关键字后面表达式的值作为返回值返回,此时可以理解为起到了return的作用
    • 可以使用next()函数让生成器从断点处继续执行,即唤醒生成器(函数)
  • 相关阅读:
    kubernetes之手动部署k8s 1.14.1高可用集群
    db2 常用配置
    db2 常用命令
    linux 常用命令
    linux for 使用
    图解Python 【第六篇】:面向对象-类-进阶篇
    图解Python 【第五篇】:面向对象-类-初级基础篇
    图解Python 【第一篇】:Python基础1
    图解Python 【第三篇】:Python-函数
    图解Python 【第二篇】:Python基础2
  • 原文地址:https://www.cnblogs.com/new-rain/p/9992682.html
Copyright © 2011-2022 走看看