zoukankan      html  css  js  c++  java
  • Python中的生成器

    列表生成式:

    • 代码演示:
      # 列表生成式
      list_1 = [x**2 for x in range(10)]  # x**2处也可以放函数
      print(list_1)   #[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
      
      # 代码等价于
      list_2 = []
      for x in range(10):
          list_2.append(x**2)
      print(list_2)
      列表生成式

    生成器:

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

    • 代码演示:
      list_1 = (x*2 for x in range(10) )
    • 比较生成器和列表生成式
      • 代码演示
        import time
        start_time = time.time()
        list_1 = (x*2 for x in range(10) )
        stop_time = time.time()
        print(list_1)
        print("the list_1 run time is %s" % (stop_time-start_time))
        
        start_time = time.time()
        list_2 = [x*2 for x in range(10) ]
        stop_time = time.time()
        print(list_2)
        print("the list_2 run time is %s" % (stop_time-start_time))
        
        """
        运行结果:
        <generator object <genexpr> at 0x0000011FACD1ED60> 生成器只有一个列表地址,并没有具体的数值
        the list_1 run time is 0.0
        [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
        the list_2 run time is 0.0
        """
        生成器和生成式的对比
      • 生成器只有在调用的时候才会生成相应的数据
      • 生成式可以直接打印列表,生成器只能打印地址
      • 生成式可以通过下角标获取元素,生成器不行
      • 生成器可以通过__next()__函数获得生成器(generator)的下一个返回值
        >>>list_1 = (x*2 for x in range(100000000))
        >>>for x in list_1:
                  print(x)
        >>>list_1.__next__
        >>>list_1.__next__
        >>>list_1.__next__
        • 只有一个__next()__用来记录当前位置,没有方法访问前面的元素,只能往后面走

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

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

        1, 1, 2, 3, 5, 8, 13, 21, 34, ...

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

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

        def fib(sum):
            a, b, c = 0, 1, 0
            while c < sum:
                #print(b)
                yield b        # 代码执行到这里,会跳出这个函数,并将b的值返回到使用next的代码处
                a, b = b, a + b
                c += 1
        # print(fib(6))  # 这里得到的就是生成器
        p = fib(6)
        print(next(p))
        print(next(p))
        print("做点别的事情")
        print(next(p))
        print(p.__next__())
        print(next(p))
        print(p.__next__())
        第二种生成器生成方式
      • 这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:
        >>> f = fib(6)
        >>> f
        <generator object fib at 0x104feaaa0
      • 这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
      • 在上面fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。
        同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:

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

        def fib(sum):
            a, b, c = 0, 1, 0
            while c < sum:
                yield b
                a, b = b, a + b
                c += 1
            return "返回值只能传递给异常"
        
        g = fib(3)
        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
        Generator return value: 返回值只能传递给异常
        """
        获取返回值的方式 

    还可通过yield实现在单线程的情况下实现并发运算的效果:

    • next()和__next__():效果相同,只是使用方式不同,都可以唤醒yield,并接收yield传过来的值。
    • send():也可以唤醒yield,也可以接收yield传递过来的值,而且,还可以在唤醒yield的同时,为yield传递一个值
    #_*_coding:utf-8_*_
    #通过生成器实现协程并行运算
    import time
    def consumer(name):
        print("%s 准备吃包子啦!" %name)
        while True:
           baozi = yield
           print("包子[%s]来了,被[%s]吃了!" %(baozi,name))
    
    def producer(name):
        c = consumer(name)
        c2 = consumer('B')
        c.__next__()
        c2.__next__()
        print("老子开始准备做包子啦!")
        for i in range(10):
            time.sleep(1)
            print("做了2个包子!")
            c.send(i) 
            c2.send(i)
    
    producer("飞某人")
  • 相关阅读:
    nowcoderD Xieldy And His Password
    Codeforces681D Gifts by the List
    nowcoder80D applese的生日
    Codeforces961E Tufurama
    Codeforces957 Mahmoud and Ehab and yet another xor task
    nowcoder82E 无向图中的最短距离
    nowcoder82B 区间的连续段
    Codeforces903E Swapping Characters
    Codeforces614C Peter and Snow Blower
    Codeforces614D Skills
  • 原文地址:https://www.cnblogs.com/fjfsu/p/15659330.html
Copyright © 2011-2022 走看看