入门理解python中的generator - 生成器

生成器 Generator 的定义

生成器(Generator)是一种特殊的函数,可以用于迭代地生成一系列值,而不需要一次性生成所有值并将它们存储在内存中。生成器在需要时逐个生成值,并在生成值后暂停执行,保留函数的状态,以便下次调用时能够从停止的地方继续执行。

生成器函数使用 yield 语句来定义,而不是常规函数中的 return 语句。当生成器函数被调用时,它返回一个生成器对象,而不是立即执行函数体。每次调用生成器对象的 next() 方法时,生成器函数将从上次执行停止的位置继续执行,直到遇到下一个 yield 语句。生成器通过生成一个值并将其返回给调用者来实现迭代。



什么是Python的生成器 Generator

上面的定义 过于官方, 理解有点困难。

通俗地讲,

  1. 生成器就是一种特别的迭代器(iterator)。
  2. 生成器的大部分使用方法是跟迭代器一样的
  3. 生成器代码更简洁, 而且具有临时修改当前元素的功能



一个简单的生成器例子

直接看代码:

def gen(num):while num > 0:yield num # return num to next() and pause the functionnum -= 1return # raise StopIterationg = gen(5) # g is a generator objectprint(next(g)) # 5
print(next(g)) # 4
print(next(g)) # 3for i in g:print(i) # 2 1
简单代码解释

1 到 5 行是 定义1个 生成器函数 gen()

第8行是基于生成器函数 定义了 了1个生成器对象 g

之后下面的代码就是可以把 g 当成iterator 来使用了, 遍历它的元素。



初步理解生成器函数

首先, 生成器 与 迭代器 1个明显的区别是, 迭代器是用类来定义的, 迭代器器的对象必须实现__next__ 函数
而且生成器是用 函数来定义的, 而里面的关键字就是 yield

一旦python 中某个函数 写入了 yield 这个关键字, 则代表python 不会把这个函数当作普通函数, 而且1个种特别的函数 - 生成器函数
生成器的函数的
yield 可以理解为return 返回1个值
而生成器最后的return 可不是普通 函数 return的意思, 实际是代表 raise StopIteration, 没有后面的元素的了的意思。



初步理解生成器对象

我们再看看上面例子的第8行

def gen(num):while num > 0:yield num # return num to next() and pause the functionnum -= 1return # raise StopIterationg = gen(5)

g = gen(5) 如果按照一般函数的思维 g 肯定是 gen() 的返回值, 由于gen()里的return 后面是吉的, 所以 g = None 吗?

实际上, 由于gen() 里面包含关键字yield, 所以它是1个生成器函数, 所以执行这g = gen(5) 时, 实际上gen()里面的代码一句都还没开始执行。

而且上面说了, gen() 里面的return 并不是真正的return , 就算我们写成 return 100, g 也不会是100!

所以我们可以认为 g = gen(5) 简单看为定义了1个g 生成器对象。



生成器函数里的代码什么时候被执行

实际上, 生成器的代码只会在 生成器对象的 next 函数调用时 才开始执行

例如上面例子中的
print(next(g))

这时, gen(5) 开始被调用



当执行到yield 时到底发生了什么

继续上面, 当gen(5) 被第一次调用时, 自然从

 while num > 0  # num = 5

这句代码开始执行, 由于num 初始值为5, 这是由 g = gen(5) 定义时决定的。
当执行到下一句代码

	yield = num

这时 gen() 函数会把num return 出去
所以 print(next(g)) 就会在console 里输出 num的值 5了, 这就是为何上面说的 yield 具有普通函数 return的功能



yield 还会中断生成器函数的执行, 并保存为下次执行的开始位置

先讲中断执行, 因为yield 具有return 的效果, 所以当执行到yield 函数就退出了
print(next(g)) 会输出数字5

很容易理解

关键是保存当前的位置,并不是很容易理解

我们继续看回例子:

def gen(num):while num > 0:yield num # return num to next() and pause the functionnum -= 1return # raise StopIterationg = gen(5) # g is a generator objectprint(next(g)) # 5
print(next(g)) # 4

这时, 第10行已经执行完成, 开始执行第11行的
print(next(g))

而这时, gen() 函数会从第5行开始执行(因为上次执行执行到 第4行

yield num

时退出了

这时执行第5行

num -= 1

num 变成了4
由于这时代码在gen()里的while里
所以 代码会返回到第2行

while num >0

这时num的值为4, 还是大于0, 再执行第3行

	yield num

这时再次中断 ,返回num

所以这时 print(next(g))就会在console 输出数字4了

关键, 当gen生成器函数 被再次调用, 它实际上是应该在上一次调用yield 的下一行开始执行



大部分情况下, yield 应该在gen()的循环体(例如while) 内使用

很明显, 想保存 生成器的函数执行位置 , yield 应该在循环体内调用, 否则就直接 到了return # raise StopInteration , 失去了迭代元素的功能

例如:
上面例子可以由while 循环改成for 循环

def gen(num): # from num to 1,  not includes 0for i in range(num, 0, -1):yield ireturn # raise StopIterationg = gen(5) # g is a generator objectprint(next(g)) # 5
print(next(g)) # 4
print(next(g)) # 3for i in g:print(i) # 2 1

为什么说大部分情况下, 因为凡事有例外, 如果1个 生成器有两个yield, 只需要保证1个yield 在循环体内



生成器 可以有多个 yield

例子:

def generate_prime_numbers():count = 1list_primes = [2]  # 用于存储已发现的素数n = 3  # 从3开始检查奇数是否是素数yield list_primes[-1] # 首先输出第一个元素2while True:is_prime = Truefor prime in list_primes:if prime * prime > n:breakif n % prime == 0:is_prime = Falsebreakif is_prime:list_primes.append(n)yield list_primes[-1] # 从第2次 到 第n次 在这里输出count += 1n += 2  # 只检查奇数是否是素数return # raise StopIteration



生成器 与 迭代器 保存当前元素的方法 的区别

由于生成器和迭代器的使用方法几乎相同。

他们都可以在遍历的过程中保存当前的元素

但是迭代器是保存在迭代器对象里的1个属性当中的。
例如:

class LinkListIterator:def __init__(self, _first_node) -> None:self._current_node = _first_nodedef __iter__(self):return selfdef __next__(self):if not self._current_node:raise StopIterationcurrent = self._current_nodeself._current_node = current.nextreturn current.value

中的self._current_node

而且生成器是利用yield 关键字保存在python 的框架中的.



生成器 支持用send() 方法临时修改当前保存的元素

我们看1个例子:

def gen(num):while num > 0:tmp = yield num # return num to next() and pause the functionif tmp:num = tmpnum -= 1return # raise StopIterationg = gen(5)
print(next(g)) # 5
print(next(g)) # 4
print(next(g)) # 3print(g.send(None)) # 2 # equal to next(g)
print(g.send(10)) # 9print("=============================================")for i in g:print(i) # 8 7 6 5 4 3 2 1

上面例子中, 我们使用了1个变量tmp 去保存 yield num的输出

然后我们就可以用g.send() 去修改这个这个tmp

然后我们用

if tmp:num = tmp

去修改num 的值, 就是实现了 临时修改 生成器步骤的功能

看例子 在15 行, g.send(None) 效果就是send 1个None 给tmp, 并不会修改num 的值, 所以这里的g.send(None) 等同于 next(g)

关键来了, 在第16行, 我们调用g.send(10) 为何输出的是9 而不是10?

当我们调用g.send(10) 时, gen() 函数会在上次调用yield 的下一行执行, 就是从第4行 if tmp:

但是第7行num-=1 会被执行, 所以下次yield输出就是9了

如果向避免这种gap可以把 num -= 1 写进else 的block里
如:

def gen(num):while num > 0:tmp = yield num # return num to next() and pause the functionif tmp:num = tmpelse:num -= 1return # raise StopIterationg = gen(5)
print(next(g)) # 5
print(next(g)) # 4
print(next(g)) # 3print(g.send(None)) # 2 # equal to next(g)
print(g.send(10)) # 10print("=============================================")for i in g:print(i) # 9 8 7 6 5 4 3 2 1

总结

好了到这里生成器的基本特性已经介绍完
我会在之后的文章里介绍生成器的一些常用使用场景

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/11053.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++】栈和队列

目录 前言 一、stack 1. 栈的定义 2. 成员函数 二、queue 1. 队列的定义 2. 成员函数 三、priority_queue 1. 优先队列的定义 2. 成员函数 前言 栈和队列是STL中很重要的两个容器,栈的特点是先进后出;队列的特点是先进先出。还有优先队列&…

kettle经验篇:MongoDB-delete插件问题

目录 项目场景 问题分析 解决方案 MongoDB Delete插件使用总结 项目场景 项目使用的ODS层数据库是MongoDB;在数据中心从DB层向ODS层同步数据过程中,发现有张ODS表在同步过程中,数据突然发生锐减,甚至于该ODS表数据清0。 同步…

算法设计与分析 例题 绘制Huffman树、循环赛、分治、最短路与动态规划

1.考虑用哈夫曼算法来找字符a,b,c,d,e,f 的最优编码。这些字符出现在文件中 的频数之比为 20:10:6:4:44:16。要求: (1)(4 分)简述使用哈夫曼算法构造最优编码的基本步骤; (2)&…

Oracle 流stream数据的复制

Oracle 流stream数据的复制 --实验的目的是捕获scott.emp1表的变化,将变化应用到远程数据库scott.emp1表中。 --设置初始化参数 AQ_TM_PROCESSES1 COMPATIBLE9.2.0 LOG_PARALLELISM1 GLOBAL_NAMEStrue JOB_QUEUE_PROCESSES2 --查看数据库的名称,我的为o…

5.11作业

1:有一个隧道,全长5公里,有2列火车,全长200米, 火车A时速 100公里每小时 火车B时速 50公里每小时 现在要求模拟火车反复通过隧道的场景(不可能2列火车都在隧道内运行…

2024中国(重庆)机器人展览会8月举办

2024中国(重庆)机器人展览会8月举办 邀请函 主办单位: 中国航空学会 重庆市南岸区人民政府 招商执行单位: 重庆港华展览有限公司 2024中国重庆机器人展会将汇聚机器人全产业链知名企业,世界科技领先的生产制造企业与来自多个国家和地区…

蓝桥杯-网络安全比赛(6) 模拟实验 Metasploit 控制并获取Windows 登录HASH、LM Hash和NTLM Hash密文解析

窃取WINDOWS账号密码 系统环境:主机(Windows系统 IP:192.168.126.129),虚拟机(KALI系统 IP:192.168.126.3),两者需要能通过本地网络互通互连。 攻击工具:Metasploit是一…

改变浏览器大小,图片(img)内容居中显示img标签,不是背景图

改变浏览器大小,图片&#xff08;img&#xff09;内容居中显示&#xff0c;img标签&#xff0c;不是背景图 效果直接上图&#xff1a; 上代码&#xff1a; <!DOCTYPE html> <html> <head><title>测试图片居中显示&#xff0c;高度不变只变宽度<…

Electron学习笔记(五)

文章目录 相关笔记笔记说明 七、系统1、系统对话框2、自定义窗口菜单3、系统右键菜单4、快捷键(1)、监听网页按键事件 &#xff08;窗口需处于激活状态&#xff09;(2)、监听全局按键事件 &#xff08;窗口无需处于激活状态&#xff09;(3)、补充&#xff1a;自定义窗口菜单快捷…

异常处理/ROS2异常处理模块源码解读与浅析

文章目录 概述ros2/rcutils/src/error_handling模块自身异常处理错误状态结构与存储本模块初始化错误状态的设置错误状态的获取错误状态的清理不丢失旧错误状态把手段还原为目的其他 概述 本文从如下几个方面对 ROS2.0 中 rcutils 库 error_handling 错误处理模块的源码进行解…

花了24小时做的采购、库存、进销存excel模板,真心好用,免费分享

花了24小时做的采购、库存、进销存excel模板&#xff0c;真心好用 在企业的日常运营中&#xff0c;进销存管理是一项至关重要的任务。它不仅涉及到商品的采购、销售和库存管理&#xff0c;还直接影响到企业的财务状况和市场竞争力。为了提高管理效率&#xff0c;许多企业选择使…

Redis 的 SDS 和 C 中字符串相比有什么优势?

C 语言使用了一个长度为 N1 的字符数组来表示长度为 N 的字符串&#xff0c;并且字符数组最后一个元素总是 \0&#xff0c;这种简单的字符串表示方式 不符合 Redis 对字符串在安全性、效率以及功能方面的要求。 C语言的字符串可能有什么问题&#xff1f; 这样简单的数据结构可…

Java程序员必知的9个SQL优化技巧

大多数的接口性能问题&#xff0c;很多情况下都是SQL问题&#xff0c;在工作中&#xff0c;我们也会定期对慢SQL进行优化&#xff0c;以提高接口性能。 这里总结一下常见的优化方向和策略。 1. 避免使用select *&#xff0c;减少查询字段 不要为了图省事&#xff0c;直接查询…

leetcode题目7

整数翻转 中等 给你一个 32 位的有符号整数 x &#xff0c;返回将 x 中的数字部分反转后的结果。 如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] &#xff0c;就返回 0。 假设环境不允许存储 64 位整数&#xff08;有符号或无符号&#xff09;。 示例 1&am…

别人家的UI表单为什么这么漂亮?而你却千篇一律。

设计漂亮的移动UI页面表单页需要考虑以下几个方面&#xff1a; 布局和结构设计 合适的布局和结构&#xff0c;使表单页面看起来整洁、清晰&#xff0c;并且易于使用。可以使用网格系统或者栅格布局来对表单进行划分&#xff0c;使不同的表单元素有明确的位置和排列。 色彩和配…

SQLite 命令

本章将向您讲解 SQLite 编程人员所使用的简单却有用的命令。这些命令被称为 SQLite 的点命令&#xff0c;这些命令的不同之处在于它们不以分号&#xff08;;&#xff09;结束。 让我们在命令提示符下键入一个简单的 sqlite3 命令&#xff0c;在 SQLite 命令提示符下&#xff0…

GO+树莓派+E53_IA1智慧农业模块

简介 之前手头上有小熊派的开发板&#xff0c; 有一个E53_IA1模块&#xff0c; 刚好用到树莓派上&#xff0c; 使用GO进行控制&#xff0c;实现智慧农业模块功能。 模块介绍 模块电路介绍 按硬件分成五块&#xff0c; 其中四块在本次用上了&#xff0c; 分别是 1. 补光模块&…

Docker nsenter 命令使用

查看容器对应宿主机上面的pid&#xff0c;容器技术的实质是进程&#xff0c;并没有完整的操作系统&#xff0c;就相当于在主机上面fork了一个子进程&#xff0c;通过docker daemon去fork一个子进程&#xff0c;这个子进程是可以在主机上面看到其pid的。 $ docker inspect -f {…

可视化大屏:城市治理方向,三维地图那是相当震撼呀。

随着城市化进程的加快&#xff0c;城市治理变得越来越复杂&#xff0c;需要大量的数据和信息来支持决策和管理。在这个背景下&#xff0c;可视化大屏作为一种新兴的信息展示工具&#xff0c;正逐渐在城市治理中发挥着重要作用。 首先&#xff0c;可视化大屏能够将庞大的数据和信…

kettle从入门到精通 第五十九课 ETL之kettle 邮件发送多个附件,使用正则轻松解决

想真正学习或者提升自己的ETL领域知识的朋友欢迎进群&#xff0c;一起学习&#xff0c;共同进步。若二维码失效&#xff0c;公众号后台加我微信入群&#xff0c;备注kettle。 问题场景&#xff1a; 一个朋友说他用kettle将生成好的多个文件&#xff08;a.xls和b.xls&#xff0…