当前位置: 首页 > 后端技术 > Python

Python函数式编程系列012:惰性列表的生成器和迭代器

时间:2023-03-26 16:05:42 Python

因为这个系列还是以一些已经熟悉Python的读者为主,所以这里就不做很多细节介绍基础知识了。但是回到我们之前的主题,我们将使用迭代器和生成器来实现之前的指数函数。当然,我们还需要回到什么是惰性列表这个问题。其实回到lazyevaluation最初的概念,lazylist的概念其实就是一个“需要的时候计算值”的列表。我们在调用iter的时候,对于普通对象其实并没有什么特别的优势。我们可以想象一下,iter转换[1,2,3,4]的结果其实是这样的:defyield_list():yield1yield2yield3yield4唯一的好处,前面已经说过了,就是应用函数重复f和g时,我们计算g(f(x)),而不是将f应用于列表中的每个值,然后再应用g。这里有一个很大的好处,就是提早终止时可以避免不必要的操作。例如,在下面的例子中,我们要发现在listls中应用f函数后,如果结果等于a,则返回index,否则返回None:deffind_index_apply_f(f,ls,a):fori,xinenumerate(ls):iff(x)==a:returnielse:continuereturnNone>>>find_index_apply_f(lambdax:x+1,[1,2,3,4,5],3)1现在,这里早点跳出是可以的减少了很多计算量,但是用普通的列表很难。我们必须在使用map之后计算所有内容,但是如果我们懒惰评估,我们可以在需要时停止。这是列表操作必须实现的,而不是循环。第二个惰性列表最大的应用是无限列表。例如,在下面的生成器中,我们可以生成一个充满无限长度x的列表。我们后面会讲到,我们在各种场合都使用过这种抽象。defyield_x_forever(x):whileTrue:yieldx实现了一些常用的(懒惰的)列表操作。大多数用于操作迭代器/生成器的函数都可以在itertoools中找到。不过我们这里还是需要实现一些非常实用的功能,方便以后的操作:1.headhead很简单,就是取出(惰性)链表的第一个元素:head=next2。take的目标是列表的前N??个值,这可以实现为触发计算(转换为非惰性对象,通常是值或列表)或不触发计算的版本。下面我们实现触发计算的函数。deftake(n,it):"""将前n个元素固定到列表中"""return[xforxinislice(it,n)]take_curry=lambdan:lambdait:take(n,it)3.dropdrop相反,删除前N个值。defdrop(n,it):"""移除前n个元素"""returnislice(it,n,None)4.tailtail是移除头部后的列表,可以通过drop实现:fromfunctoolsimportpartialtail=partial(drop,1)5.iterateiterate是要用到的关键函数,它是通过一个迭代函数和初值来实现一个无限列表:defiterate(f,x):yieldxyieldfromiterate(f,f(x))例如实现所有正偶数的无限列表:positive_even_number=iterate(lambdax:x+2,2)当然更简单的写法是在itertools中使用repeat和accumulate:defiterate(f,x):returnaccumulate(repeat(x),lambdafx,_:f(fx))简单实用示例1:找索引我们回到之前找索引的例子,我们可以实现的版本惰性列表。第一个思路,我们只是用iterate从x开始,每次乘以x,然后取出前n个值,得到最后一个:power=lambdax,n:take(n,iterate(lambdaxx:xx*x,x))[-1]另一种是先生成一个无限长的x,取出前n,乘以reduce:power=lambdax,n:reduce(lambdax,y:x*y,take(n,iterate(lambda_:x,x)))当然我们也可以使用生成器生成无限列表:defyield_power(x,init=x):yieldinityieldfromyield_power(x,init*x)示例2:查找让我们回到上面解释的示例,我们希望在无限列表中应用f后找到等于a值的第一个索引。如果不是偷懒,这肯定是提前跳出来的,是不可能实现的。deffind_a_in_lazylist(f,lls,a):returnhead(filter(lambdax:f(x[1])==a,enumerat(lls)))[0]迭代器实现惰性列表并展示如何将这些概念应用到一些数据处理应用程序。当然,这其中,我们不得不深刻感受到函数式编程非常接近数据,它关注的是数据而不是项目结构,这与基于对象的编程有很大的不同。大多数基于对象的编程教程倾向于概述诸如分层和结构之类的概念,实际上是因为这是基于对象的编程的优势所在。在我的教学项目fppy(点此进入github)中,我使用了内置的python模块实现了一个LazyList类,可以用来链式写完上面所有的例子:power1=lambdax,n:LazyList.from_iter(x)(lambdaxx:x*x).take(n).lastpower2=lambdax,n:LazyList.from_iter(x)(lambda_:x).take(n).reduce(lambdaxx,yy:xx*yy)find_a_in_lazylist=lambdaf,lls,a:LazyList(lls)\.zip_with(LazyList.from_iter(0)(lambdax:x+1))\.filter(lambdax:f(x[1])==a)\.split_head()[0]