Python3 生成器(generator)概念浅析

2023-11-12

python-generator.png

引子

某次面试问候选人:Python 中生成器是什么?答曰:有 yield 关键字的函数。而在我印象中此种函数返回的值是生成器,而函数本身不是。如下:

In [1]: def get_nums(n): 
   ...:     for i in range(n): 
   ...:         yield i 
   ...:                                                                                                                                                                  
In [2]: type(get_nums)
Out[2]: function
  
In [3]: nums = get_nums(10)   
  
In [4]: type(nums)
Out[4]: generator

但看候选人那么笃定,隐隐然感觉哪里不对,于是有了以下探究。

概念

要弄清楚 Python3 中这些概念的区别,最权威的当然是去看官方的术语对照表

generator

​ A function which returns a generator iterator. It looks like a normal function except that it contains yield expressions for producing a series of values usable in a for-loop or that can be retrieved one at a time with the next() function.

​ Usually refers to a generator function, but may refer to a generator iterator in some contexts. In cases where the intended meaning isn’t clear, using the full terms avoids ambiguity.

generator iterator

​ An object created by a generator function.

​ Each yield temporarily suspends processing, remembering the location execution state (including local variables and pending try-statements). When the generator iterator resumes, it picks up where it left off (in contrast to functions which start fresh on every invocation).

generator expression

​ An expression that returns an iterator. It looks like a normal expression followed by a for clause defining a loop variable, range, and an optional if clause.

可以看出,Python generator 相关的概念主要有三个,分别是 generatorgenerator expressiongenerator iterator。上面也特别指出了, genrator 在有的上下文中指的是 generator function(如候选人所说),但在另外一些上下文中却指的是 generator iterator(如上面例子解释器告诉我们的)。为了避免歧义,大家在表达时可以尽量说全称。

结合我的一些经验,可以将其归纳为以下三个概念:

  • Generator Function:含有 yield 关键字的函数,会返回一系列值,可以使用 next() 对其返回值进行迭代。
  • Generator Iterator:generator function 返回的对象。可以进行一次性地迭代。
  • Generator Expression:可以求值为 generator iterator 的表达式。使用小括号和 for 来定义,如下面例子:
In [5]: a = (i*i for i in range(10))                                                                                                                                                                                                                                     

In [6]: type(a)                                                                                                                                                                                                                                                          
Out[6]: generator

深入

生成器 vs 迭代器

从中文字面可能不好理解它们的关系,但是从上文提到的英文术语来分析:generator iterator。他们的关系就一目了然了:

迭代器(Iterator)是一种更宽泛的概念,生成器(generator Iterator)是一种迭代器,但反过来不成立。

迭代器是任何实现了 __next__ 方法的对象(object),可以通过 next(iterator) 对其进行迭代,迭代结束时会抛出 StopIteration 异常。

while True:
  try:
    x = next(an_iterator)
    do_sth_with(x)
  except StopIteration:
    break
 

通常我们会使用 for in 来对其进行简化迭代:

for x in an_iterator:
  do_sth_with(x)

需要python全套入门学习资料的小伙伴私信领取即可

yield 原理

yield 是一个神奇的关键字,它会临时挂起当前函数,记下其上下文(包括局部变量、待决的 try catch 等),将控制权返回给函数调用者。当下一次再调用其所在 generator function 时,会恢复保存的上下文,继续执行剩下的语句,直到再遇到 yield 或者退出为止。

我们常见的 return 是与之相对的关键字,但 return 会结束函数调用,销毁上下文(弹出栈帧),将控制权返回给调用者。

因此,以 yield 进行执行流控制的函数称为 generator function,以 return 进行执行流控制的函数,就是普通的 function 喽~

当然,由于可以临时挂起函数的执行,yield 还有更高阶的用法,即充当其调用者和被挂起函数间交互的桥梁:

In [1]: def dynamic_step_seq(size, start=0, default_step=1): 
    ...:   x = start 
    ...:   for _ in range(size): 
    ...:     given_step = yield x 
    ...:     if given_step is not None: 
    ...:       x += given_step 
    ...:     else: 
    ...:       x += default_step 
    ...:                                                                                                                                                                                                                                                                 

In [2]: for x in dynamic_step_seq(10): 
    ...:     print(x, end=' ') 
    ...: print()                                                                                                                                                                                                                                                         
0 1 2 3 4 5 6 7 8 9 
In [3]: dss = dynamic_step_seq(10)                                                                                                                                                                                                                                      

In [4]: next(dss) # 注                                                                                                                                                                                                                                                
Out[5]: 0

In [6]: dynamic_step = 1 
    ...: while True: 
    ...:     try: 
    ...:         x = dss.send(dynamic_step) 
    ...:         dynamic_step += 1 
    ...:         print(x, end=' ') 
    ...:     except StopIteration: 
    ...:         print() 
    ...:         break 
    ...:                                                                                                                                                                                                                                                                 
1 3 6 10 15 21 28 36 45 
  

:此处初看有些奇怪,但是通过 yield 作用我们能推断出原理:需要首先调用 next 将函数运行至 yield 处,才能通过 generator.send 给 generator 传送对象。

效用

那么使用生成器有什么好处呢?简单来说,主要有两大好处:

  • 精简代码
  • 提高性能

精简代码

使用 yield 关键字或者生成器表达式可以很方便的生成一个迭代器对象。为了说明这一点,我们来比较一下对于一个需求的不同实现。该需求很简单:获取前 n 个自然数。

最直观的方法为,构造一个数组然后返回:

# Build and return a list
def firstn(n):
    num, nums = 0, []
    while num < n:
        nums.append(num)
        num += 1
    return nums

sum_of_first_n = sum(firstn(1000000))

当 n 很小的时候,该实现没有什么问题,但是当 n 变得很大,你的机器内存是吃不消的:

In [4]: a = firstn(10000000000000000000)                                                                                                                                                                                                                                 
Killed

IPython 直接内存爆掉被 kill 了。

于是,我们很自然的想起可以用生成器模式,但是你仍然不想用 yield,于是你需要构造一个对象,并实现 __iter____next__ 方法:

# Using the generator pattern (an iterable)
class firstn(object):
    def __init__(self, n):
        self.n = n
        self.num, self.nums = 0, []

    def __iter__(self):
        return self

    def __next__(self):
        if self.num < self.n:
            cur, self.num = self.num, self.num+1
            return cur
        else:
            raise StopIteration()

sum_of_first_n = sum(firstn(1000000))

:在 Python3 中,如果你的类实现了__iter__ ,则成为一个可迭代对象,可以调用 iter(instance) 得到一个迭代器。如果你的类实现了 __next__,那么你的类实例本身就成为了一个迭代器,可以通过 next(instance) 来调用,进行迭代。

需要python全套入门学习资料的小伙伴私信领取即可

这些代码终于实现了我们的要求。但是,它有如下问题:

  1. 为了实现一个简单的需求却不得不构造冗长的代码。
  2. 使用了很多语言约定,读起来不直观。
  3. 逻辑表达的绕来绕去。

于是,你忍不住了,终于使用了 yield:

# a generator that yields items instead of returning a list
def firstn(n):
    num = 0
    while num < n:
        yield num
        num += 1

sum_of_first_n = sum(firstn(1000000))

于是你使用了寥寥几行,构造出了一个生成器函数。

其实[坏笑],Python 3 中,就有该函数: range(n)

利用此函数以及 Iterator expression,你可以用很紧凑的代码构造出很多迭代数列

squares = (x * x for x in range(n))
evens = (2 * x for x in range(n))

注意,他们都只能迭代一次。

提高性能

这一条主要是针对内存使用上来说的。因为迭代器不会保存所有值,而是在运行中动态的计算出数列的各个值,并将之前的数值扔掉,因此是很省内存的,这点在前面的例子也有体现。这里举另外一个更实际一点例子。

假设我们有一个很大的文件(比如说 8G) ,但是你的电脑只有 4G 内存,你如何利用 Python 对其进行处理?

答案是使用 yield 构造 generator Iterator:

def read_by_chunks(file, chunk_size=1024):
    while True:
        data = file.read(chunk_size)
        if not data:
            break
        yield data


f = open('your_big_file.dat')
for chunk in read_by_chunks(f):
    process_chunk(chunk)

这种通过构造 generator 逐块读取的方法又叫惰性加载,也叫流式读取,是处理大文件的一种常见方式。如果你的是文本文件,可以按行读取,那代码就更简单了:

with open('your_big_file.txt') as f:
    for line in f: 
        process_line(line)

Python3 文件类句柄就是一个迭代器,默认会将文件按行分割以惰性加载。

需要python全套入门学习资料的小伙伴私信领取即可

参考

  1. Python 3 术语对照表:docs.python.org/3/glossary.…

  2. Python 3 wiki:wiki.python.org/moin/Genera…

  3. Iterator vs Iterable: stackoverflow.com/questions/5…

  4. Lazy read big file:stackoverflow.com/questions/5…

最后,真心希望大家都能坚持下去,早日学会Python编程。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python3 生成器(generator)概念浅析 的相关文章

  • (discord.py) 尝试更改成员角色时,“用户”对象没有属性“角色”

    因此 我正在尝试编写一个机器人 让某人在命令中指定的主持人指定的一段时间内暂停角色 我知道该变量称为 小时 即使它目前以秒为单位 我稍后会解决这个问题 基本上 它是由主持人在消息 暂停 personmention numberofhours
  • Python BigQuery 存储。并行读取多个流

    我有以下玩具代码 import pandas as pd from google cloud import bigquery storage v1beta1 import os import google auth os environ G
  • Pycharm Python 控制台不打印输出

    我有一个从 Pycharm python 控制台调用的函数 但没有显示输出 In 2 def problem1 6 for i in range 1 101 2 print i end In 3 problem1 6 In 4 另一方面 像
  • DreamPie 不适用于 Python 3.2

    我最喜欢的 Python shell 是DreamPie http dreampie sourceforge net 我想将它与 Python 3 2 一起使用 我使用了 添加解释器 DreamPie 应用程序并添加了 Python 3 2
  • 导入错误:没有名为 _ssl 的模块

    带 Python 2 7 的 Ubuntu Maverick 我不知道如何解决以下导入错误 gt gt gt import ssl Traceback most recent call last File
  • 如何打印没有类型的defaultdict变量?

    在下面的代码中 from collections import defaultdict confusion proba dict defaultdict float for i in xrange 10 confusion proba di
  • Python 多处理示例不起作用

    我正在尝试学习如何使用multiprocessing但我无法让它发挥作用 这是代码文档 http docs python org 2 library multiprocessing html from multiprocessing imp
  • 如何等到 Excel 计算公式后再继续 win32com

    我有一个 win32com Python 脚本 它将多个 Excel 文件合并到电子表格中并将其另存为 PDF 现在的工作原理是输出几乎都是 NAME 因为文件是在计算 Excel 文件内容之前输出的 这可能需要一分钟 如何强制工作簿计算值
  • __del__ 真的是析构函数吗?

    我主要用 C 做事情 其中 析构函数方法实际上是为了销毁所获取的资源 最近我开始使用python 这真的很有趣而且很棒 我开始了解到它有像java一样的GC 因此 没有过分强调对象所有权 构造和销毁 据我所知 init 方法对我来说在 py
  • Python 中的二进制缓冲区

    在Python中你可以使用StringIO https docs python org library struct html用于字符数据的类似文件的缓冲区 内存映射文件 https docs python org library mmap
  • python 集合可以包含的值的数量是否有限制?

    我正在尝试使用 python 设置作为 mysql 表中 ids 的过滤器 python集存储了所有要过滤的id 现在大约有30000个 这个数字会随着时间的推移慢慢增长 我担心python集的最大容量 它可以包含的元素数量有限制吗 您最大
  • 使用 OpenPyXL 迭代工作表和单元格,并使用包含的字符串更新单元格[重复]

    这个问题在这里已经有答案了 我想使用 OpenPyXL 来搜索工作簿 但我遇到了一些问题 希望有人可以帮助解决 以下是一些障碍 待办事项 我的工作表和单元格数量未知 我想搜索工作簿并将工作表名称放入数组中 我想循环遍历每个数组项并搜索包含特
  • ExpectedFailure 被计为错误而不是通过

    我在用着expectedFailure因为有一个我想记录的错误 我现在无法修复 但想将来再回来解决 我的理解expectedFailure是它会将测试计为通过 但在摘要中表示预期失败的数量为 x 类似于它如何处理跳过的 tets 但是 当我
  • Python - 在窗口最小化或隐藏时使用 pywinauto 控制窗口

    我正在尝试做的事情 我正在尝试使用 pywinauto 在 python 中创建一个脚本 以在后台自动安装 notepad 隐藏或最小化 notepad 只是一个示例 因为我将编辑它以与其他软件一起使用 Problem 问题是我想在安装程序
  • Numpy 优化

    我有一个根据条件分配值的函数 我的数据集大小通常在 30 50k 范围内 我不确定这是否是使用 numpy 的正确方法 但是当数字超过 5k 时 它会变得非常慢 有没有更好的方法让它更快 import numpy as np N 5000
  • 在python中,如何仅搜索所选子字符串之前的一个单词

    给定文本文件中的长行列表 我只想返回紧邻其前面的子字符串 例如单词狗 描述狗的单词 例如 假设有这些行包含狗 hotdog big dog is dogged dog spy with my dog brown dogs 在这种情况下 期望
  • 循环标记时出现“ValueError:无法识别的标记样式 -d”

    我正在尝试编码pyplot允许不同标记样式的绘图 这些图是循环生成的 标记是从列表中选取的 为了演示目的 我还提供了一个颜色列表 版本是Python 2 7 9 IPython 3 0 0 matplotlib 1 4 3 这是一个简单的代
  • 在 Python 类中动态定义实例字段

    我是 Python 新手 主要从事 Java 编程 我目前正在思考Python中的类是如何实例化的 我明白那个 init 就像Java中的构造函数 然而 有时 python 类没有 init 方法 在这种情况下我假设有一个默认构造函数 就像
  • Python - 字典和列表相交

    给定以下数据结构 找出这两种数据结构共有的交集键的最有效方法是什么 dict1 2A 3A 4B list1 2A 4B Expected output 2A 4B 如果这也能产生更快的输出 我可以将列表 不是 dict1 组织到任何其他数
  • Pandas 与 Numpy 数据帧

    看这几行代码 df2 df copy df2 1 df 1 df 1 values 1 df2 ix 0 0 我们的教练说我们需要使用 values属性来访问底层的 numpy 数组 否则我们的代码将无法工作 我知道 pandas Data

随机推荐

  • windows linux环境搭建

    1 windows中linux环境wsl 2 powershell scoop https zhuanlan zhihu com p 463284082 powershell通过scoop可以安装各种软件 安装git就是 scoop ins
  • 提升UE5写实效果的项目设置

    随着虚幻引擎5 Unreal Engine 5 简称UE5 的发布 游戏开发者和数字艺术家们迎来了一个全新的机会 可以在其强大的渲染引擎下创建更加逼真和令人惊叹的游戏和虚拟场景 然而 要实现出色的写实效果 需要合理设置项目并运用一些技巧和策
  • 使用IDEA2023创建Servlet模板,使其右键显示Servlet选项

    使用IDEA2023创建Servlet模板 使其右键显示Servlet选项 之前在IDEA2022及以前可以通过一些方法创建Servlet模板 但我不知道为什么2023版本没有作用 下面提供另一种方式实现Servlet的创建 直接参考IDE
  • 系统化程序分析

    左志强 南京大学计算机系副研究员 研究领域包括程序分析 编译技术 系统软件等 本文以技术文章的方式回顾左老师在 SIG 程序分析 技术沙龙上的分享 回顾视频也已经上传 B 站 欢迎小伙伴们点开观看 SIG 程序分析技术沙龙回顾 面向千万行代
  • 单电源运放和双电源运放及其供电方式选择与转换的注意事项

    文章目录 前言 一 运放之双电源供电和单电源供电 1 如何区分单电源运放和双电源运放 2 单电源供电运放特性 3 运放的两种供电模式转换 4 单端偏置的缺陷 二 仿真验证 1 两阶高通滤波放大电路 两端偏置 2 两阶高通滤波放大电路 单端偏
  • 20-10-026-安装-KyLin-2.6.0-单机版安装(MAC官网下载)-spark引擎

    文章目录 1 视界 1 官网 2 安装要求 2 1 软件要求 2 2 硬件要求 2 3 Hadoop 环境 3 本次环境 4 HBASE 1 2 0安装 5 kylin 安装 6 检查zk jar 7 启动Hbase 8 添加依赖 9 添加
  • VS附加到进程调试

    操作 要附加到进程中调试外部可执行文件 您需要使用Visual Studio的 调试附加 功能 以下是附加到进程中调试外部可执行文件的步骤 打开您要调试的源代码文件或可执行文件 打开Visual Studio 选择 调试 菜单 然后选择 附
  • ffmpeg读取rtsp并保存到mp4文件

    本文章只讲述mp4文件的录像 至于音频录入 会在下个文章中介绍 总体思路为 初始化 连接相机获取码流 读取码流中的视频 创建输出mp4上下文 写mp4头 循环读取码流 写入mp4 写文件尾 关闭文件 第一步 初始化网络环境 环境注册 av
  • 信号、signal 函数、sigaction 函数

    文章目录 1 信号的基本概念 2 利用 kill 命令发送信号 3 信号处理的相关动作 4 信号与 signal 函数 4 1 signal 函数示例一 4 2 signal 函数示例二 5 利用 sigaction 函数进行信号处理 6
  • mysql对表的操作

    mysql对表的操作 表的概念 表是包含数据库中所有数据的数据库对象 表中的数据库对象包含列 索引 触发器 其中触发器是指用户定义的事务命令集合 当对一个表中的数据进行插入 更新或者删除时 这组命令就会自动执行 可以确保数据的安全性和完整性
  • npm : 无法加载文件 D:\Nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本

    npm 无法加载文件 D Nodejs node global npm ps1 因为在此系统上禁止运行脚本 1 问题详情 2 解决方法 1 问题详情 npm 无法加载文件 D Nodejs node global npm ps1 因为在此系
  • Kudu-客户端API编程、生态整合(Spark、Flink、Impala)

    文章目录 Kudu客户端API编程 客户端API核心类 Java编程接口 环境准备 创建表 插入数据 查询数据 修改表结构 更新数据 删除数据 更新和插入 删除表 Hadoop生态整合 整合概述 集成Spark Spark shell中操作
  • computed中不能写异步逻辑也就是不能发请求,如何解决

    其实不好解决 哈哈 不过仔细想想有以下几种解决方案 1 computed中的数据只要变化 computed值就会动态计算 所以你只要在交互之处 比如input 点击事件等操作中 发请求改得到结果赋值给相应的影响computed的data值
  • [1179]hive的lateral view用法

    文章目录 1 lateral view 简介 2 实操 2 1 建表 hive 2 2 插入数据 2 3 转成多行 2 4 汇总求和 1 lateral view 简介 hive函数 lateral view 主要功能是将原本汇总在一条 行
  • 关于附件下载的路径处理

    在网站附件下载中 往往我们不要直接暴露附件的存放地址 比如 a href file test doc 我的成功可以复制 a 点击下载的时候链接就是 http 192 169 1 87 file test zip 这样总感觉不够好 太直接了
  • JP《乡村振兴振兴战略下传统村落文化旅游设计》许少辉书香续,山水长

    JP 乡村振兴振兴战略下传统村落文化旅游设计 许少辉书香续 山水长
  • 条件编译小结

    编码的时候经常要用到条件编译 每次都到网上去查比较浪费时间 今天总结一下以备后用 编译器 GCC ifdef GNUC if GNUC gt 3 GCC3 0以上 Visual C ifdef MSC VER 非VC编译器很多地方也有定义
  • UnityVR--小程序4--第一人称控制器

    在没有VR设备的情况下 可以在Windows系统中运行我们之前做好的小游戏 只需要将VR场景中的OVRPlayerController更换成我们自己制作的第一人称控制器就行 之后可以用键盘和鼠标控制人物的移动 跳跃 转向 就和普通的3D游戏
  • 实用科研网站(自用)

    网站 网址 Papers With Code https paperswithcode com AMiner https www aminer cn Connected Papers https www connectedpapers co
  • Python3 生成器(generator)概念浅析

    引子 某次面试问候选人 Python 中生成器是什么 答曰 有 yield 关键字的函数 而在我印象中此种函数返回的值是生成器 而函数本身不是 如下 In 1 def get nums n for i in range n yield i