• python进阶系列 - 13讲 生成器generator


    生成器可以暂停和恢复的函数,返回一个可迭代的对象

    那为啥我们需要一个生成器了?

    生成器不像列表,本质是懒加载的,只在需要时才会生成元素。
    所以,当处理大型数据集时,生成器会更加有效。
    生成器也是普通函数,仅仅使用yield语句代替return而已。

    简单的例子:

    def my_generator():
        yield 1
        yield 2
        yield 3
    
    print(my_generator)
    for i in my_generator():
        print(i)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    结果:

    
    1
    2
    3
    
    • 1
    • 2
    • 3
    • 4

    执行生成器函数

    使用生成器函数时,需要使用next()函数来获取下一个值。

    当调用next()第一次时,执行从函数开始到第一个yield语句的位置,并继续执行直到第一个yield语句的右边的值被返回。

    如果一直调用next()但已经没有对应的yield,那么会抛出StopIteration异常,相当于生成器已经没有数据了,但还在试图获取数据,则直接报错了!

    代码:

    def countdown(num):
        print('Starting')
        while num > 0:
            yield num
            num -= 1
    cd = countdown(3)
    print(next(cd))
    print(next(cd))
    print(next(cd))
    print(next(cd))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    结果:

    Starting
    3
    2
    1
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    /var/folders/p9/ny6y7zmj0qdblv697s3_vj040000gn/T/ipykernel_99634/880898684.py in 
          8 print(next(cd))
          9 print(next(cd))
    ---> 10 print(next(cd))
    
    StopIteration: 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    生成器用于循环

    可以直接用for进行遍历,直到无元素返回。

    代码:

    def countdown(num):
        print("Starting")
        while num > 0:
            yield num
            num -= 1
    
    cd = countdown(3)
    for x in cd:
        print(x)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    结果:

    Starting
    3
    2
    1
    
    • 1
    • 2
    • 3
    • 4

    生成器用于迭代

    我们可以将生成器,类比为列表,使用相关的内置函数sumsorted一样可以很好的工作。如下代码:

    def countdown(num):
        print("Starting")
        while num > 0:
            yield num
            num -= 1
    
    cd = countdown(3)
    # 直接调用sum
    sum_cd = sum(cd)
    print(sum_cd)
    
    cd = countdown(3)
    # 直接调用sorted
    sorted_cd = sorted(cd)
    print(sorted_cd)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    结果:

    Starting
    6
    Starting
    [1, 2, 3]
    
    • 1
    • 2
    • 3
    • 4

    生成器的优势:节省内存!

    既然生成器的值是懒加载的,那么它就可以节省内存。

    例如,当需要时,生成器才会需要的数据,后续数据还未开始进行计算处理,所以生成器可以在所有元素生成之前开始使用。

    我们通过两个例子,看对比使用与不用生成器是的内存 对比。

    不使用生成器

    下面代码计算[0-1000000)的和,但中间使用列表来存储所有数字,如下代码:

    def firstn(n):
        num, nums = 0, []
        while num < n:
            nums.append(num)
            num += 1
        return nums
    
    sum_of_first_n = sum(firstn(1000000))
    print(sum_of_first_n)
    import sys
    print(sys.getsizeof(firstn(1000000)), "bytes")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    结果:

    499999500000
    8697472 bytes
    
    • 1
    • 2

    使用生成器

    类似上面使用列表,这次我们改为yield来返回数字,如下代码:

    def firstn(n):
        num = 0
        while num < n:
            yield num
            num += 1
    
    sum_of_first_n = sum(firstn(1000000))
    print(sum_of_first_n)
    import sys
    print(sys.getsizeof(firstn(1000000)), "bytes")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    结果:

    499999500000
    128 bytes
    
    • 1
    • 2

    看到了吗?内存占比只有128 bytes 了?神奇不

    实例: 斐波那契数列

    斐波那契数列(Fibonacci sequence),又称黄金分割数列,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……。

    我们用yield来返回每个斐波那契数,如下代码:

    def fibonacci(limit):
        a, b = 0, 1 # 初始值
        while a < limit:
            yield a
            a, b = b, a + b
    fib = fibonacci(30)
    print(list(fib))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    结果:

    [0, 1, 1, 2, 3, 5, 8, 13, 21]
    
    • 1

    生成器表达式

    就像列表推导一样,生成器也可以使用同样的语法,只是用圆括号()而不是方括号[]

    小心不要混淆它们,因为生成器表达式比列表推导式慢得多,因为它们调用函数的时间开销比列表推导多得多。

    示例代码:

    import sys
    # 生成器表达式
    mygenerator = (i for i in range(1000) if i % 2 == 0)
    print(sys.getsizeof(mygenerator), "bytes")
    
    # 列表表达式
    mylist = [i for i in range(1000) if i % 2 == 0]
    print(sys.getsizeof(mylist), "bytes")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    结果:

    120 bytes
    4272 bytes
    
    • 1
    • 2

    生成器的概念

    我们也可以将类实现为一个可迭代对象, 但必须实现__iter____next__从而可以跟踪当前状态(这里是当前数字),并且处理StopIteration

    看下面的代码,可以很好地理解生成器的内部原理:

    class firstn:
        def __init__(self, n):
            self.n = n
            self.num = 0
        def __iter__(self):
            return self
     
        # __next__ 方法
        def __next__(self):
            if self.num < self.n:
                cur = self.num
                self.num += 1
                return cur
            else:
                raise StopIteration()
    firstn_object = firstn(1000000)
    print(sum(firstn_object))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    结果:

    499999500000
    
    • 1

    小节

    生成器函数,外表看上去像是一个函数,但是没有用return语句一次性的返回整个结果对象列表,取而代之的是使用yield语句一次返回一个结果。

    生成器函数返回一个迭代器,for循环等迭代环境对这个迭代器不断调用next函数,不断的运行到下一个yield语句,逐一取得每一个返回值,直到没有yield语句可以运行,最终引发StopIteration异常。

    希望大家能理解到生成器的精髓!

    感谢你的阅读。欢迎大家点赞、收藏、支持!

    pythontip 出品,Happy Coding!

    公众号: 夸克编程

    We are in pythontip.com

    我们的小目标: 让天下木有难学的Python

  • 相关阅读:
    macOS系统查找被占用的端口号的操作
    pytest+yaml实现接口自动化框架
    PHP - Xdebug安装 - 学习/实践
    智慧灯杆网关管理平台:城市建设的智慧化之道
    kubernetes审计日志auditing
    TensorFlow实战教程(十七)-Keras搭建分类神经网络及MNIST数字图像案例分析
    兄弟DCP-7080激光打印机硒鼓清零方法
    JAVA sql 查询2
    如何给玩偶建模并让它跳个舞?
    Java集成阿里云的实时语音识别遇到的一些问题
  • 原文地址:https://blog.csdn.net/pythontip/article/details/126760204