当前位置: 首页 > news >正文

科普:Python中的生成器——带`yield`的函数

Python函数输出,除return 外,还有 yield ,这就是本文谈的“生成器”。

一、什么是生成器

生成器(generator):按需动态产出数据,而不会一次性把全部结果放入内存。在大数据处理时,常用它来降低内存需求。
核心标志是:函数内部写yield

  • yield的函数 →生成器函数
  • 调用生成器函数,得到返回对象 →生成器对象(不会立刻执行函数体)

yield:生成产出一个值,然后暂停函数;下次迭代,从暂停位置继续执行。即:你要,我才生成;不要,我就不生成;遍历一遍就结束。

示例:
1、产生生成器对象,并没有获得返回值

def gen_func(): yield 10 yield 20 yield 30 g = gen_func() # 仅仅拿到生成器对象,函数体没有运行 print(g) # <generator object gen_func at ...>

2、读取生成器两种方式:

#方式1 next()手动取 print(next(g)) #10 print(next(g)) #20 #方式2 for循环自动迭代(最常用) for item in gen_func(): print(item)

3、特性:生成器只能遍历一次,遍历完毕直接耗尽,不能重复使用

g = gen_func() lst1 = [x for x in g] lst2 = [x for x in g] print(lst1) # [10,20,30] print(lst2) # [] 已经耗尽,拿不到任何数据

对比普通return函数(列表)

普通函数一次性计算全部结果,全部存入内存。

def normal_func(): return [10,20,30] res = normal_func() # [10,20,30]全部已经存在内存,可以反复读取
普通列表/return函数生成器 yield
数据产生时机调用函数,一次性全部算完迭代的时候,才现场生成一个
内存占用全部数据驻留内存只保存程序运行状态,不存全部结果
重复读取可以多次循环遍历1次就耗尽

通俗比喻:

  • 列表:工厂一次性把所有商品全部生产堆进仓库。
  • 生成器:按需生产,要一件,现场做一件,做完暂停。

二、生成器实例

机器学习中的n-折方法中,每次只需对一种划分进行处理,显然符合生成器的特征要求,故常用生成器来处理。

1.kf.split(df)生成器实例(sklearn)

from sklearn.model_selection import KFold import pandas as pd df = pd.DataFrame({"v":range(10)}) kf = KFold(n_splits=5, shuffle=False) gen = kf.split(df) # gen是生成器对象,此时没有计算任何fold下标 # for循环每一轮,触发内部yield,生成一组(tr_idx,val_idx) for tr_idx, val_idx in gen: print("train下标", tr_idx) print("val下标", val_idx)

简化的内部伪代码,看懂yield逻辑:

def mock_split(n_samples,n_splits): indices = list(range(n_samples)) for fold in range(n_splits): #计算本折训练、验证下标 val_mask = [...] tr_idx = indices[~val_mask] val_idx = indices[val_mask] yield tr_idx, val_idx # 产出,暂停 #每一次for循环,执行到yield,返回一组下标

不会预先把5套下标数组全部生成保存在内存;迭代到哪一折,才生成那一折的数据

2.生成器转列表list(生成器)

list()完整消费整个生成器,把所有yield产出的值存进普通列表。

gen = kf.split(df) all_folds = list(gen) print(len(all_folds)) #5,5折,列表存储5组(tr_idx,val_idx)元组 #转list之后,支持多次循环、下标访问 tr0,val0 = all_folds[0] #但是原来的gen已经耗尽,无法再迭代 for t,v in gen: print(t) #无输出

工程取舍:

  1. 大数据(千万量数级样本):不要list(),直接for迭代生成器,内存只保留当前折数组。
    因为:如果把kf.split(df)全部list保存,大数据场景会额外消耗大量内存。
for tr_idx, val_idx in kf.split(df): #做OOF特征工程 pass
  1. 小数据集,需要多次复用划分all_folds = list(kf.split(df))

如果不想list占用内存,又要复用划分:固定random_state,重新调用kf.split(df)

三、其他常见生成器写法、场景

①生成器表达式,圆括号()

lst = [x**2 for x in range(5)] #列表推导,全部存入内存 gen = (x**2 for x in range(5)) #生成器表达式,惰性 for i in gen: print(i)

②读取超大文件(文件对象是迭代器,生成器思想)

不需要一次性把几十GB文件全部读入内存,逐行读取:

with open("big_file.txt","r") as f: for line in f: #一次只拿一行 pass

③分块读取超大csv

def read_chunk(path): for chunk in pd.read_csv(path,chunksize=10000): yield chunk for part_df in read_chunk("huge.csv"): #处理每一块数据 pass

④sklearn其他返回生成器API

TimeSeriesSplit.split()GroupKFold.split(),行为和KFold完全一致。

四、重要易错点举例

  1. ❌生成器不能下标索引
gen = kf.split(df) print(gen[0]) #报错 TypeError,生成器不支持[]取元素
  1. ❌生成器遍历一次就空
g = (i for i in range(3)) print(list(g)) #[0,1,2] print(list(g)) #[]
  1. 区分:迭代器≠生成器>

生成器属于迭代器的一种;但迭代器不一定是生成器。

lst = [1,2,3] it = iter(lst) #迭代器,但没有yield,不是生成器 next(it)

附:Python函数输出(return / yield 之外)的其它方式

  • return:函数正常返回一个(或多个)返回值,函数直接结束。
  • yield:生成器函数,产出值、暂停函数,多次产出,得到生成器对象。

除这两个,函数向外传递数据还有下面几类方式。

1. 修改外部可变对象(无return,直接改传入的容器)

函数不返回东西,直接修改传入的可变对象(list、dict、numpy数组、pandas df)。
可变对象在函数内外是同一份内存。

defadd_item(lst):lst.append(100)#直接修改外部传入的列表,没有returnmy_list=[1,2,3]add_item(my_list)print(my_list)# [1, 2, 3, 100]

pandas、numpy大量代码用这种模式;很多方法inplace=True就是这个原理。

不可变对象(int、str、tuple)这种方式无效:

deff(x):x=x+1a=10f(a)print(a)# 10,没有变化,int不可变

2. yield from (委托子生成器)

不是替换yield,是生成器内部委托另一个生成器,把子生成器所有值逐个产出。

defsub_gen():yield1yield2defmain_gen():yieldfromsub_gen()#把sub_gen全部产出交给外层yield3g=main_gen()print(list(g))# [1,2,3]

等价手写:

defmain_gen():forvinsub_gen():yieldvyield3

3. raise 抛出异常(不是返回值,向外抛出错误信号)

函数不返回结果,向上抛出异常,交由调用方捕获处理。

defdiv(a,b):ifb==0:raiseZeroDivisionError("分母不能为0")returna/b# div(10,0) #会抛出异常,程序中断,除非try捕获

区别:return是“给结果正常结束”;raise是“报告出错,异常退出”。

4. 全局变量 / nonlocal 修改外层变量(不推荐工程使用)

函数内部修改全局作用域变量,实现向外输出,可读性差,工程尽量避免。

res=0defcalc(x):globalres res=x*2calc(5)print(res)#10

5. print / stdout 标准输出(仅仅打印,不是函数返回值

很多新手混淆:print只是控制台打印文字,函数返回值是None

defhello():print("hello world")ret=hello()print(ret)# None,print输出到屏幕,并没有作为返回值交给变量ret

6. 回调函数 callback(把结果传给外部传入的函数)

函数不把结果返回给自己调用方,把结果丢给传入的回调函数。

defcompute(a,b,callback):s=a+b callback(s)#把结果传给回调函数defmy_cb(val):print(f"得到结果:{val}")compute(3,5,my_cb)#输出:得到结果:8

异步IO、很多库底层大量使用回调模式。

7. 闭包 nonlocal:内层函数修改外层函数变量

defouter():count=0definner():nonlocalcount count+=1returncountreturninner f=outer()print(f())#1print(f())#2

8. 类实例属性(面向对象方式)

函数(方法)把结果存到实例对象的成员变量,外部读取实例属性拿结果。

classResultHolder:def__init__(self):self.val=Nonedefcalc(self,x):self.val=x**2#计算结果存入实例属性obj=ResultHolder()obj.calc(6)print(obj.val)#36

http://www.cnnetsun.cn/news/4299293.html

相关文章:

  • Tiny JPEG在Chrome中发灰?一文讲透色度子采样与浏览器渲染的真相
  • AI失控风险与可控性实践:从赫拉利警示到本地大模型安全部署
  • 2026 时序基础模型:大模型不只聊天,还能预测设备何时会坏(MonkeyCode 云端实战)
  • Vibe Coding 实战:用自然语言打造有设计感的个人网站
  • 当技术教程遇到法律边界:内容策划的合规之道
  • Jmeter接口测试与性能测试实战:从环境搭建到结果分析
  • 102个Python实战项目合集:从基础语法到框架开发的完整学习路线
  • HAMP-LIC:基于Hessian的混合精度训练后量化,破解图像压缩模型部署难题
  • Java八股文天花板典藏版开源:大厂面试考点全解析与备战指南
  • 确定性、可计算性与预测边界:从混沌系统到停机问题的工程启示
  • 网易2019实习生招聘编程题全解析:考点、代码与考场策略
  • 椒盐音乐+音乐标签:本地音乐曲库整理与批量修改实践指南
  • 机器人自动分拣项目实战:从ROS、OpenCV到机械臂控制的完整开发复盘
  • Mermaid流程图代码化:从手绘到Git管理的工程实践
  • Codex CLI实战:从零生成服装品牌官网与常见报错排查
  • Java面试100题精讲:从八股文到底层原理的进阶指南
  • 阵列型SiPM探测器连接器线缆选型与管脚设计优化技术规范
  • 从混凝土箭头到GPS:跨大陆信标航线的导航革命
  • 基于YOLOv5的煤矿大块煤识别数据集构建与训练实践
  • 具身智能数据闭环实战:从真机采集到仿真回流的基础设施部署
  • 从450亿美元算力大单看大模型训练与推理基础设施
  • 从RAID到NFC:绿联私有云DH4300 Plus让家庭存储更简单
  • 用Vibe Coding 13天开发怀旧挂机游戏:AI辅助编程实践
  • WinForm自定义打印设计工具:从可视化设计到动态数据打印的完整实现
  • JavaScript基础快速入门:2小时从核心语法到交互实战
  • DGX Spark机器学习环境配置实战:从驱动到多机推理全指南
  • AI编程辅助Cheat Engine Lua脚本开发实战指南
  • 指人游戏搬到网页:实现线上聚会互动玩法的技术指南
  • WASI 0.3.1:WebAssembly系统接口能力模型与工程实践
  • AI时代产品经理核心壁垒:从问题定义到结果验证