zoukankan      html  css  js  c++  java
  • python生成器

    知识内容:

    1.列表生成式

    2.生成器介绍

    3.生成器函数

    一、列表生成式

    1.需求:  列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],要求你把列表中的每个值都加1,你如何实现

    实现上述需求其实不难,有如下两种方法

    (1)普通青年版

    1 >>> a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    2 >>> for index, i in enumerate(a):
    3 ...     a[index] += 1
    4 ...
    5 >>> print(a)
    6 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10

    (2)文艺青年版

     1 >>> a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
     2 >>> a = map(lambda x:x+1, a)
     3 >>> a
     4 <map object at 0x04CC38B0>
     5 >>> for i in a:
     6 ...     print(i)
     7 ...
     8 1
     9 2
    10 3
    11 4
    12 5
    13 6
    14 7
    15 8
    16 9
    17 10

    其实还有第3种方法,如下:

    (3)装逼青年版

    1 >>> a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    2 >>> a = [i+1 for i in a]
    3 >>> a
    4 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    这样的写法就叫列表生成式

    2.列表生成式语法

    (1)列表生成式使用非常简洁的方式来快速生成满足特定需求的列表,代码具有非常强的可读性,例如:

    1 a = [x*x for x in range(10)]

    上面的代码等价于:

    1 a = []
    2 for x in range(10):
    3     a.append(x*x)
     1 # # 以下3段代码等价:
     2 # f = [' banana ', ' loganberry ', ' passion fruit ']
     3 # a = [w.strip() for w in f]
     4 # print(a)
     5 #
     6 # f = [' banana ', ' loganberry ', ' passion fruit ']
     7 # for i, v in enumerate(f):
     8 #     f[i] = v.strip()
     9 # print(a)
    10 #
    11 # f = [' banana ', ' loganberry ', ' passion fruit ']
    12 # f = list(map(str.strip, f))
    13 # print(a)
    列表生成式

    (2)循环与列表生成式

     1 # ======一层循环======
     2 l = [i*i for i in range(1,10)]
     3 print(l)
     4 # 上面的列表推倒式就相当于下面的
     5 l  = []
     6 for i in range(1,10):
     7     l.append(i*i)
     8 print(l)
     9 l = []
    10 
    11 
    12 # ======多层循环========
    13 # 1.列表推倒式
    14 l = [i*j for i in range(1,10) for j in range(1,10)]
    15 print(l)
    16 # 2.循环
    17 l = []
    18 for i in range(1,10):
    19     for j in range(1,10):
    20         s = i*j
    21         l.append(s)
    22 print(l)

    二、生成器介绍

    1.生成器定义

    通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都浪费了。所以如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

    生成器的好处,就是一下子不会在内存中生成太多的数据

    2.生成器的本质:就是一个迭代器

    3.使用生成器

    (1)创建生成器

    创建生成器有两种方法: 生成器表达式和生成器函数,生成器表达式如下所示,生成器函数见后面详解

    把列表生成式中的[]改成()就可以创建一个generator,这样来创建生成器的方法叫生成器表达式

    1 >>> a = (i for i in range(10))
    2 >>> a
    3 <generator object <genexpr> at 0x03004F00>

    (2)next函数

    我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?

    如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值:

     1 >>> next(a)
     2 0
     3 >>> next(a)
     4 1
     5 >>> next(a)
     6 2
     7 >>> next(a)
     8 3
     9 >>> next(a)
    10 4
    11 >>> next(a)
    12 5
    13 >>>
    14 >>> next(a)
    15 6
    16 >>> next(a)
    17 7
    18 >>>
    19 >>> next(a)
    20 8
    21 >>> next(a)
    22 9
    23 >>> next(a)
    24 Traceback (most recent call last):
    25   File "<stdin>", line 1, in <module>
    26 StopIteration

    注:  generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误!  因为生成器还是可迭代对象,所以遍历生成器我们依然使用for循环,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误

    (3)遍历生成器

    for循环遍历:

    1 >>> a = (i for i in range(10))
    2 >>> for i in a:
    3 ...     print(i, end=" ")
    4 ...
    5 0 1 2 3 4 5 6 7 8 9

     while循环遍历(结合异常处理):

    1 s = (i for i in range(10))
    2 
    3 while True:
    4     try:
    5         x = next(s)
    6         print(x)
    7     except StopIteration as e:
    8         print('Generator return value:', e.value)
    9         break

    三、生成器函数

    1.生成器函数实例

    generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

    比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到

    斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

    1 def fib(max):
    2     n, a, b = 0, 0, 1
    3     while n < max:
    4         print(b)
    5         a, b = b, a + b
    6         n = n + 1
    7     return "OK"

    仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

    1 def fib(max):
    2     n,a,b = 0,0,1
    3 
    4     while n < max:
    5         #print(b)
    6         yield  b
    7         a,b = b,a+b
    8         n += 1
    9     return "OK"

    这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

    >>> f = fib(6)
    >>> f
    <generator object fib at 0x104feaaa0>

    这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行

    2.生成器函数定义

    生成器函数是常规定义函数,但是,使用yield语句而不是return语句返回结果,yield语句一次返回一个结果。第一次调用生成器函数是生成了一个生成器,然后在每次调用next()的时候执行生成器函数,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行

     1 def fib(max):
     2     n, a, b = 0, 0, 1
     3     while n < max:
     4         # print(b)
     5         print("before yield")
     6         yield b          # 把函数的执行冻结在这一步,并把b的值返回给外面的next()
     7         print(b)
     8         a, b = b, a + b
     9         n += 1
    10 
    11 
    12 f = fib(15)     # turn function into a generator
    13 next(f)
    14 next(f)
    15 next(f)
    16 next(f)

    注: 只要函数中有yield,解释器就就会将其看作一个生成器!

    3.关于range函数

    在python2中range返回的是list,xrange返回的是生成器对象;而python3中只有range,没有xrange,并且在python3中range返回的是生成器对象

    用生成器实现range函数:

     1 def range_wyb(n):
     2     count = 0
     3     while count < n:
     4         # print(count)
     5         yield count
     6         count += 1
     7 
     8 
     9 # range_wyb(12)
    10 x = range_wyb(12)        # 创建一个生成器对象
    11 print(x)
    12 # next(x)
    13 # next(x)
    14 # next(x)
    15 for i in x:                       # 遍历生成器
    16     print(i, end=" ")            

    4.yield、return、next的区别

    • return: 返回并终止函数
    • yield: 返回数据并冻结当前的执行过程
    • next:  唤醒冻结的函数执行过程,继续执行,直到遇到下一个yield

    另外,函数有了yield之后:

    • 函数名加上()就得到了一个生成器
    • return在生成器里,代表生成器的中止,直接报错

    5.总结

    关于生成器:

    • 只有在调用时才会生成值
    • 只记录当前位置
    • 可以用生成器表达式或生成器函数生成
    • 只有一个__next__方法,next()
    • 生成器也可以使用for循环

    关于生成器函数:

    • 语法上和函数类似
    • 自动实现迭代器协议
    • 状态挂起:通过yield实现状态挂起

    生成器的优缺点:

    优点:

    延迟计算,一次返回一个结果,它不会一次生成所有的结果,这对于大数据处理非常有用

    生成器还能有效提高代码可读性

    6.单线程实现并行(协程)

     1 # 通过生成器实现协程并行运算 (生成者消费者模型)
     2 import time
     3 
     4 
     5 def consumer(name):
     6     print("%s 准备吃包子啦!" % name)
     7     while True:
     8        baozi = yield            # 保存当前状态并返回
     9        print("包子[%s]来了,被[%s]吃了!" % (baozi, name))
    10 
    11 
    12 c = consumer("woz")
    13 c.__next__()
    14 b1 = "猪肉馅"
    15 c.send(b1)          # send->给yield传值
    16 
    17 
    18 def producer(name):
    19     c = consumer('A')
    20     c2 = consumer('B')
    21     c.__next__()
    22     c2.__next__()
    23     print("%s开始准备做包子啦!" % name)
    24     for i in range(10):
    25         time.sleep(1)
    26         print("做了2个包子!")
    27         c.send(i)
    28         c2.send(i)
    29 
    30 
    31 producer("Wyb")

    注:消费函数调用后要next的原因:

    消费函数调用是生成一个生成器,并未执行里面的代码,只有next之后才能执行里面的代码然后到yield返回

  • 相关阅读:
    ArcGIS
    gdal 在 vs2005中的安装
    ArcView ArcEditor ArcInfo区别
    从自行车掉链想到的
    实现带有身份验证的WebService
    研究一下全文索引
    【转】Com和.Net互操作规范
    TDSSNIClient 初始化失败,出现错误 0x7e,状态代码 0x60。
    整理最近新学到的几种sql写法(一)动态“循环”更新表内数据
    整理多个技术点的sql综合应用:(开拓思路,SELECT子句内嵌子查询)
  • 原文地址:https://www.cnblogs.com/wyb666/p/8920060.html
Copyright © 2011-2022 走看看