享受Python中的函数式编程

编辑:光环大数据 来源: 互联网 时间: 2017-10-20 11:43 阅读:

  尽管Python事实上并不是一门纯函数式编程语言,但它本身是一门多范型语言,并给了你足够的自由利用函数式编程的便利。函数式风格有着各种理论与实际上的好处(你可以在Python的文档中找到这个列表):

形式上可证

模块性

组合性

易于调试及测试

虽然这份列表已经描述得够清楚了,但我还是很喜欢MichaelO.Church在他的文章“函数式程序极少腐坏(Functionalprogramsrarelyrot)”中对函数式编程的优点所作的描述。我在PyConUA2012期间的讲座“FunctionalProgrammingwithPython”中谈论了在Python中使用函数式方式的内容。我也提到,在你尝试在Python中编写可读同时又可维护的函数式代码时,你会很快发现诸多问题。

fn.py类库就是为了应对这些问题而诞生的。尽管它不可能解决所有问题,但对于希望从函数式编程方式中获取最大价值的开发者而言,它是一块“电池”,即使是在命令式方式占主导地位的程序中,也能够发挥作用。那么,它里面都有些什么呢?

Scala风格的Lambda定义

在Python中创建Lambda函数的语法非常冗长,来比较一下:

Python

map(lambdax:x*2,[1,2,3])

List(1,2,3).map(_*2)

Clojure

(map#(*%2)'(123))

Haskell

map(2*)[1,2,3]

受Scala的启发,Fn.py提供了一个特别的_对象以简化Lambda语法。

fromfnimport_

assert(_+_)(10,5)=15

assertlist(map(_*2,range(5)))==[0,2,4,6,8]

assertlist(filter(_<10,[9,10,11]))==[9]

除此之外还有许多场景可以使用_:所有的算术操作、属性解析、方法调用及分片算法。如果你不确定你的函数具体会做些什么,你可以将结果打印出来:

fromfnimport_

print(_+2)#"(x1)=>(x1+2)"

print(_+_*_)#"(x1,x2,x3)=>(x1+(x2*x3))"

流(Stream)及无限序列的声明

Scala风格的惰性求值(Lazy-evaluated)流。其基本思路是:对每个新元素“按需”取值,并在所创建的全部迭代中共享计算出的元素值。Stream对象支持<<操作符,代表在需要时将新元素推入其中。

惰性求值流对无限序列的处理是一个强大的抽象。我们来看看在函数式编程语言中如何计算一个斐波那契序列。

Haskell

fibs=0:1:zipWith(+)fibs(tailfibs)

Clojure

(deffib(lazy-cat[01](map+fib(restfib))))

Scala

deffibs:Stream[Int]=

0#::1#::fibs.zip(fibs.tail).map{case(a,b)=>a+b}

现在你可以在Python中使用同样的方式了:

fromfnimportStream

fromfn.itersimporttake,drop,map

fromoperatorimportadd

f=Stream()

fib=f<<[0,1]<<map(add,f,drop(1,f))

assertlist(take(10,fib))==[0,1,1,2,3,5,8,13,21,34]

assertfib[20]==6765

assertlist(fib[30:35])==[832040,1346269,2178309,3524578,5702887]

蹦床(Trampolines)修饰符

fn.recur.tco是一个不需要大量栈空间分配就可以处理TCO的临时方案。让我们先从一个递归阶乘计算示例开始:

deffact(n):

ifn==0:return1

returnn*fact(n-1)

这种方式也能工作,但实现非常糟糕。为什么呢?因为它会递归式地保存之前的计算值以算出最终结果,因此消耗了大量的存储空间。如果你对一个很大的n值(超过了sys.getrecursionlimit()的值)执行这个函数,CPython就会以此方式失败中止:

>>>importsys

>>>fact(sys.getrecursionlimit()*2)

...manymanylinesofstacktrace...

RuntimeError:maximumrecursiondepthexceeded

这也是件好事,至少它避免了在你的代码中产生严重错误。

我们如何优化这个方案呢?答案很简单,只需改变函数以使用尾递归即可:

deffact(n,acc=1):

ifn==0:returnacc

returnfact(n-1,acc*n)

为什么这种方式更佳呢?因为你不需要保留之前的值以计算出最终结果。可以在Wikipedia上查看更多尾递归调用优化的内容。可是……Python的解释器会用和之前函数相同的方式执行这段函数,结果是你没得到任何优化。

fn.recur.tco为你提供了一种机制,使你可以使用“蹦床”方式获得一定的尾递归优化。同样的方式也使用在诸如Clojure语言中,主要思路是将函数调用序列转换为while循环。

fromfnimportrecur

@recur.tco

deffact(n,acc=1):

ifn==0:returnFalse,acc

returnTrue,(n-1,acc*n)

@recur.tco是一个修饰符,能将你的函数执行转为while循环并检验其输出内容:

(False,result)代表运行完毕

(True,args,kwargs)代表我们要继续调用函数并传递不同的参数

(func,args,kwargs)代表在while循环中切换要执行的函数

函数式风格的错误处理

假设你有一个Request类,可以按照传入其中的参数名称得到对应的值。要想让其返回值格式为全大写、非空并且去除头尾空格的字符串,你需要这样写:

classRequest(dict):

defparameter(self,name):

returnself.get(name,None)

r=Request(testing="Fixed",empty="")

param=r.parameter("testing")

ifparamisNone:

fixed=""

else:

param=param.strip()

iflen(param)==0:

fixed=""

else:

fixed=param.upper()

额,看上去有些古怪。用fn.monad.Option来修改你的代码吧,它代表了可选值,每个Option实例可代表一个Full或者Empty(这点也受到了Scala中Option的启发)。它为你编写长运算序列提供了简便的方法,并且去掉除了许多if/else语句块。

fromoperatorimportmethodcaller

fromfn.monadimportoptionable

classRequest(dict):

@optionable

defparameter(self,name):

returnself.get(name,None)

r=Request(testing="Fixed",empty="")

fixed=r.parameter("testing")

.map(methodcaller("strip"))

.filter(len)

.map(methodcaller("upper"))

.get_or("")

fn.monad.Option.or_call是个便利的方法,它允许你进行多次调用尝试以完成计算。例如,你有一个Request类,它有type,mimetype和url等几个可选属性,你需要使用最少一个属性值以分析它的“request类型”:

fromfn.monadimportOption

request=dict(url="face.png",mimetype="PNG")

tp=Option\

.from_value(request.get("type",None))\#check"type"keyfirst

.or_call(from_mimetype,request)\#or..check"mimetype"key

.or_call(from_extension,request)\#or...get"url"andcheckextension

.get_or("application/undefined")

其余事项?

我仅仅描述了类库的一小部分,你还能够找到并使用以下功能:

22个附加的itertools代码段,以扩展内置module的功能的附加功能

将Python2和Python3的迭代器(iterator)(如range,map及filtter等等)使用进行了统一,这对使用跨版本的类库时非常有用

为函数式组合及partial函数应用提供了简便的语法

为使用高阶函数(apply,flip等等)提供了附加的操作符

正在进行中的工作

自从在Github上发布这个类库以来,我从社区中收到了许多审校观点、意见和建议,以及补丁和修复。我也在继续增强现有功能,并提供新的特性。近期的路线图包括以下内容:

为使用可迭代对象(iterable),如foldl,foldr增加更多操作符

更多的monad,如fn.monad.Either,以处理错误记录

为大多数module提供C-accelerator

为简化lambdaarg1:lambdaarg2:…形式而提供的curry函数的生成器

更多文档,更多测试,更多示例代码

  Python培训,就选光环大数据Python培训机构python学习地址:http://hadoop.aura.cn/python/

 


大数据培训、人工智能培训、Python培训、大数据培训机构、大数据培训班、数据分析培训、大数据可视化培训,就选光环大数据!光环大数据,聘请专业的大数据领域知名讲师,确保教学的整体质量与教学水准。讲师团及时掌握时代潮流技术,将前沿技能融入教学中,确保学生所学知识顺应时代所需。通过深入浅出、通俗易懂的教学方式,指导学生更快的掌握技能知识,成就上万个高薪就业学子。 更多问题咨询,欢迎点击------>>>>在线客服

你可能也喜欢这些

在线客服咨询

领取资料

X
立即免费领取

请准确填写您的信息

点击领取
#第三方统计代码(模版变量) '); })();
'); })();