Skip to content
SRE运维进阶之路SRE运维进阶之路
github icon
    • 1 Python 简介

      • 1.1 初识Python
        • 1.2 Python 代码规范
          • 1.3 Python 虚拟环境
            • 1.4 使用 vscode 打造 python 开发环境
              • 1.5 pypi 配置国内镜像
              • 2 Python 基础

                • 2.1 Python基础语法
                  • 2.2 程序控制
                    • 2.3 Python数据类型

                      • 2.3.1 数值型
                        • 2.3.2 字符串 str
                          • 2.3.3 字节序列
                            • 2.3.4 列表 list & 元组 tuple
                              • 2.3.5 集合 set & 字典 dict
                            • 3 高级特性

                              • 3.1 线性结构特征 可迭代 & 切片
                                • 3.2 列表、集合、字典解析式
                                  • 3.3 生成器
                                    • 生成器表达式
                                      • 生成器函数
                                      • 3.4 迭代器
                                      • 4 函数

                                        • 4.1 函数的定义 & 调用 & 返回值
                                          • 4.2 函数参数
                                            • 4.3 作用域
                                              • 4.4 递归函数
                                              • 5 函数式编程

                                                • 5.1 高阶函数
                                                  • 5.2 返回函数
                                                    • 5.3 匿名函数
                                                      • 5.4 装饰器
                                                        • 5.5 偏函数
                                                        • 6 模块

                                                          • 6.1 Python 模块常用的几种安装方式
                                                            • 6.2 Python 的 setup.py 详解
                                                            • 7 IO编程

                                                              • 7.1 操作文件和目录
                                                                • 7.2 序列化和反序列化
                                                                • 8 异常、调试和测试

                                                                  • 8.1 异常处理
                                                                  • 9 面向对象编程

                                                                    • 9.1 类、实例和封装
                                                                      • 9.2 访问控制和属性装饰器
                                                                        • 9.3 继承、多态和Mixin
                                                                        • 10 进程和线程

                                                                          • 10.1 多进程
                                                                            • 10.2 多线程
                                                                              • 10.2 线程同步
                                                                              • 11 网络编程

                                                                                • 11.1 SocketServer
                                                                                  • 11.2 TCP 编程
                                                                                  • 11 魔术方法
                                                                                    • 17 IO 模型
                                                                                      • python 实际工作中的实例
                                                                                      • 前端学习笔记

                                                                                        3.3 生成器

                                                                                        author iconClaycalendar icon2021年6月22日category icon
                                                                                        • Python
                                                                                        timer icon大约 6 分钟

                                                                                        此页内容
                                                                                        • 生成器表达式
                                                                                        • 生成器函数

                                                                                        # 3.3 生成器

                                                                                        要创建一个generator,有很多种方法。

                                                                                        # 生成器表达式

                                                                                        第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator

                                                                                        语法

                                                                                        • (返回值 for 元素 in 可迭代对象 if 条件)
                                                                                        • 列表解析式的中括号换成小括号就行了
                                                                                        • 返回一个生成器对象

                                                                                        和列表解析式的区别

                                                                                        • 生成器表达式是按需计算(或称惰性求值、延迟计算),需要的时候才计算值,返回可迭代对象迭代器,只能迭代一次
                                                                                        • 列表解析式是立即返回值,返回可迭代对象列表,不是迭代器,可反复迭代

                                                                                        生成器对象

                                                                                        • 可迭代对象
                                                                                        • 迭代器

                                                                                        生成器表达式和列表解析式对比

                                                                                        • 计算方式
                                                                                          • 生成器表达式延迟计算,列表解析式立即计算
                                                                                        • 内存占用
                                                                                          • 单从返回值本身来说,生成器表达式省内存,列表解析式返回新的列表
                                                                                          • 生成器没有数据,内存占用极少,但是使用的时候,虽然一个个返回数据,但是合起来占用的内存也差不多
                                                                                          • 列表解析式构造新的列表需要立即占用掉内存
                                                                                        • 计算速度
                                                                                          • 单看计算时间看,生成器表达式耗时非常短,列表解析式耗时长
                                                                                          • 但生成器本身并没有返回任何值,只返回了一个生成器对象
                                                                                          • 列表解析式构造并返回了一个新的列表

                                                                                        例子:

                                                                                        >>> 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>
                                                                                        
                                                                                        1
                                                                                        2
                                                                                        3
                                                                                        4
                                                                                        5
                                                                                        6

                                                                                        创建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
                                                                                        
                                                                                        1
                                                                                        2
                                                                                        3
                                                                                        4
                                                                                        5
                                                                                        6
                                                                                        7
                                                                                        8
                                                                                        9
                                                                                        10
                                                                                        11
                                                                                        12
                                                                                        13
                                                                                        14
                                                                                        15
                                                                                        16
                                                                                        17
                                                                                        18
                                                                                        19
                                                                                        20
                                                                                        21
                                                                                        22
                                                                                        23
                                                                                        24

                                                                                        我们讲过,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
                                                                                        
                                                                                        1
                                                                                        2
                                                                                        3
                                                                                        4
                                                                                        5
                                                                                        6
                                                                                        7
                                                                                        8
                                                                                        9
                                                                                        10
                                                                                        11
                                                                                        12
                                                                                        13
                                                                                        14

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

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

                                                                                        # 生成器函数

                                                                                        # 生成器函数 # 调用后返回什么呢?生成器对象(生成器表达式、生成器函数),惰性求值
                                                                                        # 在一个函数定义中,出现了yield语句,此函数就是生成器函数
                                                                                        def foo():
                                                                                        	while True:
                                                                                        		yield 1
                                                                                        # 无限可迭代对象
                                                                                        f = foo()
                                                                                        
                                                                                        1
                                                                                        2
                                                                                        3
                                                                                        4
                                                                                        5
                                                                                        6
                                                                                        7

                                                                                        generator非常强大。如果推算的算法比较复杂,用类似列表生成式的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'
                                                                                        
                                                                                        1
                                                                                        2
                                                                                        3
                                                                                        4
                                                                                        5
                                                                                        6
                                                                                        7

                                                                                        注意,赋值语句:

                                                                                        a, b = b, a + b
                                                                                        
                                                                                        1

                                                                                        相当于:

                                                                                        t = (b, a + b) # t是一个tuple
                                                                                        a = t[0]
                                                                                        b = t[1]
                                                                                        
                                                                                        1
                                                                                        2
                                                                                        3

                                                                                        但不必显式写出临时变量t就可以赋值。

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

                                                                                        >>> fib(6)
                                                                                        1
                                                                                        1
                                                                                        2
                                                                                        3
                                                                                        5
                                                                                        8
                                                                                        'done'
                                                                                        
                                                                                        1
                                                                                        2
                                                                                        3
                                                                                        4
                                                                                        5
                                                                                        6
                                                                                        7
                                                                                        8

                                                                                        仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

                                                                                        也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

                                                                                        def fib(max):
                                                                                            n, a, b = 0, 0, 1
                                                                                            while n < max:
                                                                                                yield b
                                                                                                a, b = b, a + b
                                                                                                n = n + 1
                                                                                            return 'done'
                                                                                        
                                                                                        1
                                                                                        2
                                                                                        3
                                                                                        4
                                                                                        5
                                                                                        6
                                                                                        7

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

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

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

                                                                                        举个简单的例子,定义一个generator,依次返回数字1,3,5:

                                                                                        def odd():
                                                                                            print('step 1')
                                                                                            yield 1
                                                                                            print('step 2')
                                                                                            yield(3)
                                                                                            print('step 3')
                                                                                            yield(5)
                                                                                        
                                                                                        1
                                                                                        2
                                                                                        3
                                                                                        4
                                                                                        5
                                                                                        6
                                                                                        7

                                                                                        调用该generator时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值:

                                                                                        >>> o = odd()
                                                                                        >>> next(o)
                                                                                        step 1
                                                                                        1
                                                                                        >>> next(o)
                                                                                        step 2
                                                                                        3
                                                                                        >>> next(o)
                                                                                        step 3
                                                                                        5
                                                                                        >>> next(o)
                                                                                        Traceback (most recent call last):
                                                                                          File "<stdin>", line 1, in <module>
                                                                                        StopIteration
                                                                                        
                                                                                        1
                                                                                        2
                                                                                        3
                                                                                        4
                                                                                        5
                                                                                        6
                                                                                        7
                                                                                        8
                                                                                        9
                                                                                        10
                                                                                        11
                                                                                        12
                                                                                        13
                                                                                        14

                                                                                        可以看到,odd不是普通函数,而是generator,在执行过程中,遇到yield就中断,下次又继续执行。执行3次yield后,已经没有yield可以执行了,所以,第4次调用next(o)就报错。

                                                                                        回到fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。

                                                                                        同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:

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

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

                                                                                        >>> g = fib(6)
                                                                                        >>> 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
                                                                                        g: 3
                                                                                        g: 5
                                                                                        g: 8
                                                                                        Generator return value: done
                                                                                        
                                                                                        1
                                                                                        2
                                                                                        3
                                                                                        4
                                                                                        5
                                                                                        6
                                                                                        7
                                                                                        8
                                                                                        9
                                                                                        10
                                                                                        11
                                                                                        12
                                                                                        13
                                                                                        14
                                                                                        15
                                                                                        16

                                                                                        关于如何捕获错误,后面的错误处理还会详细讲解。

                                                                                        edit icon编辑此页open in new window
                                                                                        上次编辑于: 2021/6/23 03:40:27
                                                                                        贡献者: clay-wangzhi
                                                                                        上一页
                                                                                        3.2 列表、集合、字典解析式
                                                                                        下一页
                                                                                        3.4 迭代器
                                                                                        备案号:冀ICP备2021007336号
                                                                                        Copyright © 2023 Clay