当前位置: 首页 > 科技观察

Python循环这样写,高效节省内存100倍!

时间:2023-03-16 16:51:30 科技观察

0前言在处理循环的时候,我们习惯使用for、while等,比如依次打印每个列表中的字符:lis=['I','love','python']foriinlis:print(i)Ilovepython当打印的内容字节数少的时候,将所有内容加载到内存后打印是没有问题的。但是,如果有几千万条车辆轨迹,要求你分析其中每个客户的出行方式和交通拥堵情况,如果你在单机上处理这个事情。你可以先面对它,也可以忽略它。代码写完之后,可能会暴露一个问题:outofmemory,这在实际项目中经常遇到。这个问题提醒我们,在处理数据时,如何编写高效使用内存的程序非常重要。今天,我们将讨论如何有效地使用内存,节省内存并同时完成工作。其实Python已经准备了一个模块专门来处理这件事情。它是itertools模块。里面的几个函数的作用其实很好理解。我无意笼统地介绍它们能实现的功能,而是想分析一下这些功能背后的实现代码,它们是如何高效节省内存的,Python内核贡献者是如何写出漂亮的代码的。这很有趣,不是吗?好了,走吧。希望你喜欢这个旅程!1拼接元素itertools中的chain函数实现了元素的拼接。原型如下,参数*表示应用可变数的参数链(iterables)如下:In[33]:list(chain(['I','love'],['python'],['very','much']))Out[33]:['I','love','python','very','much']哇,不能用了。它有点像join,但比join更好。它的重点是参数都是可迭代的实例。那么,chain是如何实现高效的内存节省的呢?chain的大致实现代码如下:defchain(*iterables):foritiniterables:forelementinit:yieldelement上面的代码不难理解,chain的本质是返回一个生成器,所以它实际上是一次读取一个元素到内存中,所以它是最有效的保存内存。2逐一累加并返回列表的累加汇总值。原型:accumulate(iterable[,func,*,initial=None])应用如下:In[36]:list(accumulate([1,2,3,4,5,6],lambdax,y:x*y))Out[36]:[1,2,6,24,120,720]accumulate的大概实现代码如下:defaccumulate(iterable,func=operator.add,*,initial=None):it=iter(iterable)total=initialifinitialisNone:try:total=next(it)exceptStopIteration:returnyieldtotalforelementinit:total=func(total,element)yieldtotal上面的代码,你还好吗?不同于chain的简单yield,这里稍微复杂一点。yield有点像return,所以yieldtotal这一行直接返回了一个元素,也就是iterable的第一个元素,因为这个函数任何时候返回的第一个元素都是它的第一个。又因为yield返回的是一个生成器对象,比如名字为gen,所以在next(gen)的时候,代码会执行到其中的for元素:行,而此时it的iterator已经指向了第二个元素iterable,OK,相信你明白了!3Funnelscreening是一个compress函数,功能类似于funnel函数,所以我称之为funnelscreening,原型:compress(data,selectors)In[38]:list(compress('abcdefg',[1,1,0,1]))Out[38]:['a','b','d']很容易看出compress返回的元素个数等于最短列表的长度两个参数。其大致实现代码:defcompress(data,selectors):return(dford,sinzip(data,selectors)ifs)这个函数对于过滤4段scanlist非常有用,保留不满足条件的部分。原型如下:dropwhile(predicate,iterable)应用示例:In[39]:list(dropwhile(lambdax:x<3,[1,0,2,4,1,1,3,5,-5]))Out[39]:[4,1,1,3,5,-5]实现它的一般代码如下:defdropwhile(predicate,iterable):iteriterable=iter(iterable)forxiniterable:ifnotpredicate(x):yieldxbreakforxiniterable:yieldx5segmentfilter2扫描列表,只要满足条件,就从iterable对象中返回元素,直到不满足条件为止。原型如下:takewhile(predicate,iterable)应用示例:In[43]:list(takewhile(lambdax:x<5,[1,4,6,4,1]))Out[43]:[1,4]实现它的大致代码如下:deftakewhile(predicate,iterable):forxiniterable:ifpredicate(x):yieldxelse:break#立即返回6次扫描的次品列表,只要不满足条件,原型如下:dropwhile(predicate,iterable)应用示例:In[40]:list(filterfalse(lambdax:x%2==0,[1,2,3,4,5,6]))Out[40]:[1,3,5]实现它的大概代码如下:defdropwhile(predicate,iterable):iteriterable=iter(iterable)forxiniterable:ifnotpredicate(x):yieldxbreakforxiniterable:yieldx7Python中的切片过滤器普通的切片操作,如:lis=[1,3,2,1]lis[:1]他们的缺陷是必须把所有lis都加载到内存中,所以比较省内存的操作islice,原型如下:islice(iterable,start,stop[,step])应用示例:In[41]:list(islice('abcdefg',1,4,2))Out[41]:['b','d']实现其大致代码如下:defislice(iterable,*args):s=slice(*args)start,stop,sstep=s.startor0,s.stoporsys.maxsize,s.stepor1it=iter(range(start,stop,step(range(i+1,stop),iterable):pass巧妙的利用生成器在迭代结束时抛出异常StopIteration,并做一些边界处理8细胞分裂tee函数类似于众所周知的细胞分裂。它可以复制n个原始迭代器。原型如下:tee(iterable,n=2)应用如下。可以看出复制的两个迭代器是独立的a=tee([1,4,6,4,1],2)In[51]:next(a[0])Out[51]:1In[52]:next(a[1])Out[52]:1实现它的代码大致如下:deftee(iterable,n=2):it=iter(iterable)deques=[collections.deque()foriinrange(n)]defgen(mydeque):whileTrue:ifnotmydeque:try:newval=next(it)exceptStopIteration:returnfordindeques:d.append(newval)yieldmydeque.popleft()returntuple(gen(d)fordindeques)tee实现在内部使用队列类型deques,最初生成一个空队列,并向每个复制的队列添加元素newval,同时生成当前调用的mydeque中最左边的元素。9map变体starmap可以看作是map的变体,可以更节省内存,同时iterable的元素也必须是可迭代对象。原型如下:starmap(function,iterable)应用它:In[63]:list(starmap(lambdax,y:str(x)+'-'+str(y),[('a',1),('b',2),('c',3)]))Out[63]:['a-1','b-2','c-3']starmap的实现细节如下如下:defstarmap(function,iterable):forargsiniterable:yieldfunction(*args)10copyelementrepeat实现复制元素n次,原型如下:repeat(object[,times])应用如下:In[66]:列表(重复(6,3))输出[66]:[6,6,6]输入[67]:列表(重复([1,2,3],2))输出[67]:[[1,2,3],[1,2,3]]其实现细节大致如下:defrepeat(object,times=None):iftimesisNone:#如果没有设置times,会一直重复whileTrue:yieldobjectelse:foriinrange(times):yieldobject11Cartesianproduct笛卡尔积效果同下:((x,y)forxinAforyinB)所以笛卡尔积实现效果如下:In[68]:list(product('ABCD','xy'))Out[68]:[('A','x'),('A','y'),('B','x'),('B','y'),('C','x'),('C','y'),('D','x'),('D','y')]它的实现细节:defproduct(*args,repeat=1):pools=[tuple(pool)forpoolinargs]*repeatresult=[[]]forpoolinpools:result=[x+[y]forxinresultforyinpool]forprodinresult:yieldtuple(prod)12增强版zip组合值。如果可迭代对象的长度没有对齐,缺失值会根据fillvalue进行填充。注意:迭代一直持续到最长的可迭代对象耗尽。效果如下:In[69]:list(zip_longest('ABCD','xy',fillvalue='-'))Out[69]:[('A','x'),('B','y'),('C','-'),('D','-')]其实现细节:defzip_longest(*args,fillvalue=None):iterators=[iter(it)foritinargs]num_active=len(迭代器)ifnotnum_active:returnwhileTrue:values=[]fori,itinenumerate(迭代器):try:value=next(it)exceptStopIteration:num_active-=1ifnotnum_active:returniterators[i]=repeat(fillvalue)value=fillvaluevalues.append(value)yieldtuple(values)在里面使用了repeat,即当可迭代对象长度不对齐时,根据fillvalue填充缺失值。理解上面代码的关键是迭代器对象(iter),next方法的特殊性:In[74]:fori,itinenumerate([iter([1,2,3]),iter(['x','y'])]):...:print(next(it))#Output:1x结合这个提示再理解上面的代码,就不难了。总结一下Python的itertools模块提供的省内存高效的迭代器,基本都是借助generators实现的,所以一方面了解这12个函数实现的基本功能,同时加深对generators的理解.为我们写出更高效、简洁、美观的代码打下坚实的基础。