人生苦短,我用Python - Python高阶函数
NicolasTGB
9年前
<blockquote> <p>人生苦短,我用Python!</p> </blockquote> <h2>0x00:函数式编程</h2> <blockquote> <p>describe what to do, rather than how to do it.</p> <p>函數式編程(英语:functional programming)或称函数程序设计,又稱泛函編程,是一種編程典範,它將電腦運算視為數學上的函數計算,並且避免使用程序状态以及易变物件。函數程式語言最重要的基礎是λ演算(lambda calculus)。而且λ演算的函數可以接受函數當作輸入(引數)和輸出(傳出值)。<br> 比起指令式編程,函數式編程更加強調程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。</p> </blockquote> <p>函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用(边界效应)。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。</p> <p><strong>三大特点</strong><br> 1.immutable data(不可变数据):<br> 默认上变量是不可变的,如果你要改变变量,你需要把变量copy出去修改</p> <p>2.first class functions<br> 这个技术可以让你的函数就像变量一样来使用。也就是说,你的函数可以像变量一样被创建,修改,并当成变量一样传递,返回或是在函数中嵌套函数。这个有点像Javascript的Prototype</p> <p>3.尾递归优化<br> 我们知道递归的害处,那就是如果递归很深的话,stack受不了,并会导致性能大幅度下降。所以,我们使用尾递归优化技术——每次递归时都会重用stack,这样一来能够提升性能,当然,这需要语言或编译器的支持。Python就不支持。</p> <p><strong>函数式编程的几个技术</strong><br> 1.<strong>map & reduce</strong><br> 基于命令式的编程语言中,如果对一个数据集进行特定操作的时候,需要使用for或者是while循环,让用户在循环语句内部进行操作,并且所有的边界条件都是用户自己定义的,所以就会出现边界溢出的bug。而map和reduce思想就是用一种更加数学化(理论化)的编程。</p> <p>2.<strong>Pipeline</strong><br> 之前map、reduce是将一组数据放到特定的函数里面进行操作,这里的操作函数一般就一个。管道(Pipeline)就像是Linux系统中的管道一样。数据依此通过不同的管道,最后输出。</p> <p>3.<strong>recuring</strong><br> 递归最大的好处就简化代码,他可以把一个复杂的问题用很简单的代码描述出来。注意:递归的精髓是描述问题,而这正是函数式编程的精髓。</p> <p>4.<strong>currying</strong><br> 把一个函数的多个参数分解成多个函数, 然后把函数多层封装起来,每层函数都返回一个函数去接收下一个参数这样,可以简化函数的多个参数。而且每一层只专注最少的功能性代码,所以你的代码可维护性将大大提高,出现bug的概率也会大大降低。</p> <p>5.<strong>higher order function</strong><br> 高阶函数:所谓高阶函数就是函数当参数,把传入的函数做一个封装,然后返回这个封装函数。现象上就是函数传进传出,就像面向对象对象满天飞一样。</p> <p>6.<strong>lazy evaluation</strong><br> 惰性求值:这个需要编译器的支持。表达式不在它被绑定到变量之后就立即求值,而是在该值被取用的时候求值,也就是说,语句如x:=expression; (把一个表达式的结果赋值给一个变量)明显的调用这个表达式被计算并把结果放置到 x 中,但是先不管实际在 x 中的是什么,直到通过后面的表达式中到 x 的引用而有了对它的值的需求的时候,而后面表达式自身的求值也可以被延迟,最终为了生成让外界看到的某个符号而计算这个快速增长的依赖树。</p> <p>7.<strong>determinism </strong><br> 确定性:所谓确定性的意思就是像数学那样 f(x) = y ,这个函数无论在什么场景下,都会得到同样的结果,这个我们称之为函数的确定性。而不是像程序中的很多函数那样,同一个参数,却会在不同的场景下计算出不同的结果。所谓不同的场景的意思就是我们的函数会根据一些运行中的状态信息的不同而发生变化。</p> <p><strong>总结</strong>:函数式编程更侧重的是让你着手于解决问题,而不是解决问题的过程或是方法,一般从命令式语言(大多数的是C++)入门的程序员都会养成thinking like a machine 的思维方式,命令式语言已经烙印在脑海中,就像那个冷笑话:<br> 妻子对程序员老公说:去买点早饭,如果有西瓜,买一个。结果程序员连早餐也没有买。<br> 老本行是数学的程序员他们对函数式编程的理解会比较深入一些,所以大家程序员们找媳妇的话还是造个数学的姑娘吧!</p> <h2>0x01:函数即数据</h2> <blockquote> <p>everything is data in computer, even all functions and your mind.</p> </blockquote> <p>对于计算机来说,尤其是CPU,送到其中的任何东西都是数据(0和1嘛)。函数或者是类的概念只是更高层次的语言模型,对于底层的硬件来说这些高层的语法和语义最终是要翻译成指令进入流水线的。所以说函数也是数据的一种,这里的数据并不是指狭义的数据,或者是在内存块中的地址,而是广义上的信息流。</p> <p>在Python中我们知道,我们可以将一个函数赋值给一个变量。变量可以当做参数传递到函数中,同时,变量也可以作为函数的返回值返回。那么这样一来,函数也能够当做参数传递到函数,或是作为返回值(装饰器一章节会讲到这个函数作为返回值)返回。</p> <p><strong>例子:</strong></p> <pre> <code class="language-python">#函数作为参数 def callFunc(func): print ("callFunc.") func() def Func(): print ("call function") callFunc(Fun)</code></pre> <pre> <code class="language-python">#函数作为返回值 def Func(): def subFunc(): print ("this is the inner function") return subFunc Func()</code></pre> <p>函数作为返回值的好处之一就是可以实现延迟计算,函数返回的只是一个功能函数并没有将运行结果返回,所以当年调用的时候,就像是被人授渔而非授鱼一样(好别扭的典故引用),只有当你需要的时候才去做计算。</p> <h2>0x02:高阶函数</h2> <blockquote> <p>thinking like a Pythonor!</p> </blockquote> <ul> <li> <p>lambda 函数</p> </li> </ul> <p>写过JavaScript的童鞋肯定知道匿名函数的作用了,匿名函数可以直接使用,定义的地方就是作用的地方,但是你没法在其他的地方调用它。因为,他没有名字,你什么也不告诉编译器,编译器也啥也不知道当然不会调用了。匿名函数就是为了便利才设计的。</p> <p>在Python中,你可以使用lambda函数来充当匿名函数的角色。lambda语句构建的其实是一个函数对象,因为你可以将函数赋值给一个变量,所以说你可以使用变量来代替匿名函数来进行调用,当然也可以结合其他的高级函数使用。</p> <pre> <code class="language-python">#将匿名函数赋值给变量 p=lambda x : x+2 print (p(2)) p2=lambda x,y: x+y print(p2(1,2))</code></pre> <p>这样一看,lambda确实没有什么大的作用嘛,就是省略了函数名嘛。事实上就是这样的,下面是些匿名函数的好处:</p> <blockquote> <ol> <li>使用Python写一些执行脚本时,使用lambda可以省去定义函数的过程,让代码更加精简。</li> <li>对于一些抽象的,不会别的地方再复用的函数,有时候给函数起个名字也是个难题,使用lambda不需要考虑命名的问题。</li> <li>使用lambda在某些时候让代码更容易理解。</li> </ol> </blockquote> <ul> <li> <p>map函数</p> </li> </ul> <blockquote> <p>map(function, iterable, ...)</p> <p>Apply function to every item of iterable and return a list of the results. If additional iterable arguments are passed, function must take that many arguments and is applied to the items from all iterables in parallel. If one iterable is shorter than another it is assumed to be extended withNoneitems. If function isNone, the identity function is assumed; if there are multiple arguments, map() returns a list consisting of tuples containing the corresponding items from all iterables (a kind of transpose operation). The iterable arguments may be a sequence or any iterable object; the result is always a list.</p> </blockquote> <p><em>先看最先一句</em>:首先,你要有一个function(这个function就是改变iterable<br> data中的每个element的功能函数,比如说元素加倍,元素取反)。然后map函数会返回一个list给你。你看不到实际的循环过程,一切都是编译器“偷偷”的完成的这些对数据的循环操作。</p> <pre> <code class="language-python">def doubleMe(para): return para*2 result=map(doubleMe,range(1,11)) #结果是result=[2,4,6,8,10,12,14,16,18,20] #可以结合上面将的lambda函数直接写成一行 result2=map(lambda x: x*2,range(1,11)) #结果是result2=[2,4,6,8,10,12,14,16,18,20],和上面的一致</code></pre> <p>你可以这么认为,function是个操作机器手臂,iterable data是个运行中的传送带,data里面的每个element就是传送带上面的每一个材料。第一句描述的是就是一个机器手臂,然后操作一条传送带(此时function只有一个参数),将每个element操作之后放在一个list中,完事之后推出这个list。</p> <p><em>再看后面加了条件的一句</em>:如果额外的iterable 参数需要传递的话,function函数需要同时去提取同位置的iterable data的元素。这一句描述的是还是一个机器手臂,但是现在有同时多个传送带在运行(function有多个参数),机器手臂会同时从这些传送带上面取数据,然后进行操作,最后还是推出list。</p> <pre> <code class="language-python">result=map(lambda x,y :x+y, range(1,5),range(6,10)) #结果是result=[7,9,11,13]</code></pre> <p>文档重点描述的是,你需要同时从这些data中取相同下标的element。后面的一句,说的是假设你其中的一个data比另一个data包含的数据少,那么你会自动将短的list填充Noneitems.</p> <pre> <code class="language-python">result=map(lambda x,y :x+y, range(1,6),range(6,10)) #结果是会报错误:TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'</code></pre> <p><em>再看最后一种情况:</em>假如我们传递给map的是一个None的function会怎么样呢,此时什么也不会发生,实际上就是做了个数据转移操作,将数据从iterable data转移到list中。</p> <pre> <code class="language-python">map(None,range(1,3)) #输出 [1,2] map(None,range(1,3),range(5,7)) #输出[(1, 5), (2, 6)] #可以看到第二个有多个参数组的情况下,只是把同下标的给“打包”了</code></pre> <p>总结:这些高级函数在高手手中真的是能省下不少力气啊,好处是消除了for 循环的“边界效应“,同时,和lambda函数同时使用,确实能让人很好的阅读和理解。像我这种从命令式语言入门的程序员来说,确实刚接触的时候很吃惊也很费解。其实抛开你对命令式语言的执念,这些更贴近数学和”白话“的语句真的很好理解了。如果你不懂什么是命令式编程和函数式编程,这一篇<a href="/misc/goto?guid=4959671457362748056">《00.编程学习--初始》</a>是从语言模型上讲述两者的区别,可以参看下。</p> <ul> <li> <p>reduce函数</p> </li> </ul> <blockquote> <p>reduce(function, iterable[, initializer])<br> Apply function of two arguments cumulatively to the items of iterable, from left to right, so as to reduce the iterable to a single value. If the optional initializer is present, it is placed before the items of the iterable in the calculation, and serves as a default when the iterable is empty. If initializer is not given and iterable contains only one item, the first item is returned.</p> </blockquote> <p>map函数讲述的是对传送带上的数据做操作之后会放到一个list里面,也就是结果的个数和传送带(不管有几个传送带在工作)上面的数据个数是一致的。同时传送带上面的零件都是一样的,比如都是汽车车门,我们的操作可以是喷漆或是焊接。有的时候我们有个机器手臂是做组装的,而且传送带上面的零件都是不同类型的,比如是车门、车架、车轮、发动机之类的,最后需要组装在一起。这里就可以用reduce函数了,他的返回是只是一个(并不是list)。<br> <em>第一种</em>:将iterable data里面的数据,从左到右,挨个取出来放到function里<br> ,function必须接受两个参数,一个就是取出来的这个元素,一个就是前面每次操作的结果。reduce的本意是消除,这里的意思就是将一大堆的数据挨个消除,最后整合(流水线装配)在一起。</p> <pre> <code class="language-python">result = reduce(lambda x,y : x+y, range(1,5)) #结果是result=10</code></pre> <p><em>第二种</em>:有的时候我们可以传递第三个参数,这是一个初始值,就像是有的时候前面的装配线已经装了部分汽车了,现在的装配线(reduce)只需要装内饰就行。如果没有初始值的话,就使用iterable data的第一个element作为初始值。</p> <pre> <code class="language-python">result = reduce(lambda x,y : x+y, range(1,5),3) #结果是result=13</code></pre> <ul> <li> <p>filter函数</p> </li> </ul> <blockquote> <p>filter(function, iterable)<br> Construct a list from those elements of iterable for which function returns true. iterable may be either a sequence, a container which supports iteration, or an iterator. If iterable is a string or a tuple, the result also has that type; otherwise it is always a list. If function is None, the identity function is assumed, that is, all elements of iterable that are false are removed.</p> </blockquote> <p>还是用生产线的例子讲解,有的时候我们需要严格把控质量,需要将不合格的零件挑选出来,这时候就需要filter了(filter本意就是筛选嘛)。filter函数的作用就是将iterable data里面符合你function的element选出来。你的function必须返回True/False。如果是你的function是None的话,那么编译器默认的function就会将iterable data 中值是False的element去除。</p> <pre> <code class="language-python">result = filter(lambda x: x>3, range(1,5)) #结果是result=[4] result = filter(None,range(1,3)) #结果是result=[1,2] result=filter(None,[False,True]) # 结果是result=[True]</code></pre> <h3>总结</h3> <p>其实这就是Python中”流水线”操作,你可以想象成汽车装配厂的流水线。</p> <blockquote> <p>map 函数:喷漆流水线(可以有多条)<br> reduce 函数:组装流水线(只有一条)<br> filter 函数:筛选流水线(只有一条)</p> </blockquote> <h2>参考</h2> <ul> <li><a href="/misc/goto?guid=4959671457454788728">函数式编程-wiki</a></li> <li><a href="/misc/goto?guid=4958852142151700034">函数式编程</a></li> <li><a href="/misc/goto?guid=4959671457563474724">函数式编程-廖雪峰</a></li> <li><a href="/misc/goto?guid=4959671457651996105">Python中map浅析</a></li> <li><a href="/misc/goto?guid=4959671457728430906">Python built-in 函数</a></li> </ul> <p><br> </p> <p><a href="/misc/goto?guid=4959671457806804784">文/北静王(简书作者)</a></p>