尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

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

科普: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)生成器实例sklearnfrom sklearn.model_selection import KFold import pandas as pd df pd.DataFrame({v:range(10)}) kf KFold(n_splits5, shuffleFalse) 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)) #55折列表存储5组(tr_idx,val_idx)元组 #转list之后支持多次循环、下标访问 tr0,val0 all_folds[0] #但是原来的gen已经耗尽无法再迭代 for t,v in gen: print(t) #无输出工程取舍大数据千万量数级样本不要list()直接for迭代生成器内存只保留当前折数组。因为如果把kf.split(df)全部list保存大数据场景会额外消耗大量内存。for tr_idx, val_idx in kf.split(df): #做OOF特征工程 pass小数据集需要多次复用划分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③分块读取超大csvdef read_chunk(path): for chunk in pd.read_csv(path,chunksize10000): yield chunk for part_df in read_chunk(huge.csv): #处理每一块数据 pass④sklearn其他返回生成器APITimeSeriesSplit.split()、GroupKFold.split()行为和KFold完全一致。四、重要易错点举例❌生成器不能下标索引gen kf.split(df) print(gen[0]) #报错 TypeError生成器不支持[]取元素❌生成器遍历一次就空g (i for i in range(3)) print(list(g)) #[0,1,2] print(list(g)) #[]区分迭代器≠生成器生成器属于迭代器的一种但迭代器不一定是生成器。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大量代码用这种模式很多方法inplaceTrue就是这个原理。不可变对象int、str、tuple这种方式无效deff(x):xx1a10f(a)print(a)# 10没有变化int不可变2. yield from 委托子生成器不是替换yield是生成器内部委托另一个生成器把子生成器所有值逐个产出。defsub_gen():yield1yield2defmain_gen():yieldfromsub_gen()#把sub_gen全部产出交给外层yield3gmain_gen()print(list(g))# [1,2,3]等价手写defmain_gen():forvinsub_gen():yieldvyield33. raise 抛出异常不是返回值向外抛出错误信号函数不返回结果向上抛出异常交由调用方捕获处理。defdiv(a,b):ifb0:raiseZeroDivisionError(分母不能为0)returna/b# div(10,0) #会抛出异常程序中断除非try捕获区别return是“给结果正常结束”raise是“报告出错异常退出”。4. 全局变量 / nonlocal 修改外层变量不推荐工程使用函数内部修改全局作用域变量实现向外输出可读性差工程尽量避免。res0defcalc(x):globalres resx*2calc(5)print(res)#105. print / stdout 标准输出仅仅打印不是函数返回值很多新手混淆print只是控制台打印文字函数返回值是None。defhello():print(hello world)rethello()print(ret)# Noneprint输出到屏幕并没有作为返回值交给变量ret6. 回调函数 callback把结果传给外部传入的函数函数不把结果返回给自己调用方把结果丢给传入的回调函数。defcompute(a,b,callback):sab callback(s)#把结果传给回调函数defmy_cb(val):print(f得到结果:{val})compute(3,5,my_cb)#输出得到结果:8异步IO、很多库底层大量使用回调模式。7. 闭包 nonlocal内层函数修改外层函数变量defouter():count0definner():nonlocalcount count1returncountreturninner fouter()print(f())#1print(f())#28. 类实例属性面向对象方式函数方法把结果存到实例对象的成员变量外部读取实例属性拿结果。classResultHolder:def__init__(self):self.valNonedefcalc(self,x):self.valx**2#计算结果存入实例属性objResultHolder()obj.calc(6)print(obj.val)#36
返回列表