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

    生成器

    1、列表生成式

      现在有个需求,看列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],要求你把列表里的每个值加1,你怎么实现?

      

    >>> a
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    >>> b = []
    >>> for i in a:b.append(i+1)
    ...
    >>> b
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    >>> a = b
    >>> a
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    方式一
    a = [1,3,4,6,7,7,8,9,11]
     
    for index,i in enumerate(a):
        a[index] +=1
    print(a)
    方式二
    >>> a
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    >>> a = map(lambda x:x+1, a)
    >>> a
    <map object at 0x101d2c630>
    >>> for i in a:print(i)
    ...
    3
    5
    7
    9
    11
    方式三

      还有一种写法,如下:

    # 1.依次取出元素 2.依次把取出的元素进行处理i+1 3.依次把处理好的元素存放到列表中
    >>> a = [i+1 for i in range(10)]
    >>> a
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    方式四

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

      还可以加if判断条件,如下:

    # 把小于5的加1取出来
    >>> a = [i+1 for i in range(10) if i < 5]
    >>> a
    [1, 2, 3, 4, 5]
    列表生成式

      列表生成式的缺点

      占内存,列表足够大会撑爆内存,解决这个问题就要用生成器加for循环

    2、生成器(generator)

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

      创建生成器的方式(把列表生成式的[]改成())
      要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个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 0x1022ef630>

      创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。
      我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?
      如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值:

    >>> next(g)
    0
    >>> next(g)
    1
    >>> next(g)
    4
    >>> next(g)
    9
    >>> next(g)
    16
    >>> next(g)
    25
    >>> next(g)
    36
    >>> next(g)
    49
    >>> next(g)
    64
    >>> next(g)
    81
    >>> next(g)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration
    调用next()获取返回值

      我们讲过,generator保存的是算法,每次调用next(g)就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
      当然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

    >>> g = (x * x for x in range(10))
    >>> for n in g:
    ...     print(n)
    ...
    0
    1
    4
    9
    16
    25
    36
    49
    64
    81

      所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。

      创建生成器的方式(yield表达式)
      如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
      比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
      1, 1, 2, 3, 5, 8, 13, 21, 34, ...

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

      上面的函数可以输出斐波那契数列的前n个数:

    >>> fib(10)
    1
    1
    2
    3
    5
    8
    13
    21
    34
    55
    done

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

    def fib(max):
        n,a,b = 0,0,1
        while n < max:
            #print(b)
            yield  b
            a,b = b,a+b
            n += 1
        return 'done'
    yield生成式

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

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

      generator和函数的执行流程不一样。
      函数是顺序执行,遇到return语句或者最后一行函数语句就返回。
      而变成generator的函数,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方时从当前位置继续运行

    data = fib(10)
    print(data)
     
    print(data.__next__())
    print(data.__next__())
    print("干点别的事")
    print(data.__next__())
    print(data.__next__())
    print(data.__next__())
    print(data.__next__())
    print(data.__next__())
     
    #输出
    <generator object fib at 0x000002E33EEFFCA8>
    1
    1
    干点别的事
    2
    3
    5
    8
    13
    范例

      在上面fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:

    >>> for n in fib(6):
    ...     print(n)
    ...
    1
    1
    2
    3
    5
    8

      但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:

    >>> g = fib(6)
    >>> while True:
    ...     try:
    ...         x = next(g)
    ...         print('g:', x)
    ...     except StopIteration as e: # 当try中的程序执行错误了,才会执行except下面的代码
    ...         print('Generator return value:', e.value)
    ...         break
    ...
    g: 1
    g: 1
    g: 2
    g: 3
    g: 5
    g: 8
    Generator return value: done
    范例

    3、小结

      生成器和列表生成式的区别:
      列表生成式占内存,有索引,取值灵活,能获取长度。
      生成器表达式省内存,用一个取一个,不能获取长度,没有索引。

     
  • 相关阅读:
    flask全栈开发3 模板
    flask全栈开发2 URL与视图
    flask全栈开发1 课程简介
    微信公众号开发中遇到的问题总结
    python web学习路线
    内存数据库Memcached和redis基本使用
    2019年8月12号成长题目
    2019年8月10号成长题目
    2019年8月7号成长题目
    SpringCloud简介与5大常用组件
  • 原文地址:https://www.cnblogs.com/windyrainy/p/10648647.html
Copyright © 2011-2022 走看看