尽管Python事实上并不是一门纯函数式编程语言,但它本身是一门多范型语言,并给了你足够的自由利用函数式编程的便利。函数式风格有着各种理论与实际上的好处(你可以在Python的文档中找到这个列表):
形式上可证
模块性
组合性
易于调试及测试
虽然这份列表已经描述得够清楚了,但我还是很喜欢Michael O.Church在他的文章“函数式程序极少腐坏(Functional programs rarely rot)”中对函数式编程的优点所作的描述。我在PyCon UA 2012期间的讲座“Functional Programming with Python”中谈论了在Python中使用函数式方式的内容。我也提到,在你尝试在Python中编写可读同时又可维护的函数式代码时,你会很快发现诸多问题。
fn.py类库就是为了应对这些问题而诞生的。尽管它不可能解决所有问题,但对于希望从函数式编程方式中获取最大价值的开发者而言,它是一块“电池”,即使是在命令式方式占主导地位的程序中,也能够发挥作用。那么,它里面都有些什么呢?
Scala风格的Lambda定义
在Python中创建Lambda函数的语法非常冗长,来比较一下:
Python
map(lambda x: x*2, [1,2,3])
Scala
代码如下:
List(1,2,3).map(_*2)
Clojure
(map #(* % 2) '(1 2 3))
Haskell
map (2*) [1,2,3]
受Scala的启发,Fn.py提供了一个特别的_对象以简化Lambda语法。
from fn import _
assert (_ + _)(10, 5) = 15
assert list(map(_ * 2, range(5))) == [0,2,4,6,8]
assert list(filter(_ <10, [9,10,11])) &#61;&#61; [9]
除此之外还有许多场景可以使用_&#xff1a;所有的算术操作、属性解析、方法调用及分片算法。如果你不确定你的函数具体会做些什么&#xff0c;你可以将结果打印出来&#xff1a;
print (_ &#43; 2) # "(x1) &#61;> (x1 &#43; 2)"
print (_ &#43; _ * _) # "(x1, x2, x3) &#61;> (x1 &#43; (x2 * x3))"
流(Stream)及无限序列的声明
Scala风格的惰性求值(Lazy-evaluated)流。其基本思路是&#xff1a;对每个新元素“按需”取值&#xff0c;并在所创建的全部迭代中共享计算出的元素值。Stream对象支持<
惰性求值流对无限序列的处理是一个强大的抽象。我们来看看在函数式编程语言中如何计算一个斐波那契序列。
fibs &#61; 0 : 1 : zipWith (&#43;) fibs (tail fibs)
(def fib (lazy-cat [0 1] (map &#43; fib (rest fib))))
def fibs: Stream[Int] &#61;
0 #:: 1 #:: fibs.zip(fibs.tail).map{case (a,b) &#61;> a &#43; b}
现在你可以在Python中使用同样的方式了&#xff1a;
from fn import Stream
from fn.iters import take, drop, map
from operator import add
f &#61; Stream()
fib &#61; f <<[0, 1] <assert list(take(10, fib)) &#61;&#61; [0,1,1,2,3,5,8,13,21,34]assert fib[20] &#61;&#61; 6765assert list(fib[30:35]) &#61;&#61; [832040,1346269,2178309,3524578,5702887]蹦床(Trampolines)修饰符fn.recur.tco是一个不需要大量栈空间分配就可以处理TCO的临时方案。让我们先从一个递归阶乘计算示例开始&#xff1a;def fact(n):if n &#61;&#61; 0: return 1return n * fact(n-1)这种方式也能工作&#xff0c;但实现非常糟糕。为什么呢&#xff1f;因为它会递归式地保存之前的计算值以算出最终结果&#xff0c;因此消耗了大量的存储空间。如果你对一个很大的n值(超过了sys.getrecursionlimit()的值)执行这个函数&#xff0c;CPython就会以此方式失败中止&#xff1a;>>> import sys>>> fact(sys.getrecursionlimit() * 2)... many many lines of stacktrace ...RuntimeError: maximum recursion depth exceeded这也是件好事&#xff0c;至少它避免了在你的代码中产生严重错误。我们如何优化这个方案呢&#xff1f;答案很简单&#xff0c;只需改变函数以使用尾递归即可&#xff1a;def fact(n, acc&#61;1):if n &#61;&#61; 0: return accreturn fact(n-1, acc*n)为什么这种方式更佳呢&#xff1f;因为你不需要保留之前的值以计算出最终结果。可以在Wikipedia上查看更多尾递归调用优化的内容。可是……Python的解释器会用和之前函数相同的方式执行这段函数&#xff0c;结果是你没得到任何优化。fn.recur.tco为你提供了一种机制&#xff0c;使你可以使用“蹦床”方式获得一定的尾递归优化。同样的方式也使用在诸如Clojure语言中&#xff0c;主要思路是将函数调用序列转换为while循环。from fn import recur&#64;recur.tcodef fact(n, acc&#61;1):if n &#61;&#61; 0: return False, accreturn True, (n-1, acc*n)&#64;recur.tco是一个修饰符&#xff0c;能将你的函数执行转为while循环并检验其输出内容&#xff1a;(False, result)代表运行完毕(True, args, kwargs)代表我们要继续调用函数并传递不同的参数(func, args, kwargs)代表在while循环中切换要执行的函数函数式风格的错误处理假设你有一个Request类&#xff0c;可以按照传入其中的参数名称得到对应的值。要想让其返回值格式为全大写、非空并且去除头尾空格的字符串&#xff0c;你需要这样写&#xff1a;class Request(dict):def parameter(self, name):return self.get(name, None)r &#61; Request(testing&#61;"Fixed", empty&#61;" ")param &#61; r.parameter("testing")if param is None:fixed &#61; ""else:param &#61; param.strip()if len(param) &#61;&#61; 0:fixed &#61; ""else:fixed &#61; param.upper()额&#xff0c;看上去有些古怪。用fn.monad.Option来修改你的代码吧&#xff0c;它代表了可选值&#xff0c;每个Option实例可代表一个Full或者Empty(这点也受到了Scala中Option的启发)。它为你编写长运算序列提供了简便的方法&#xff0c;并且去掉除了许多if/else语句块。from operator import methodcallerfrom fn.monad import optionableclass Request(dict):&#64;optionabledef parameter(self, name):return self.get(name, None)r &#61; Request(testing&#61;"Fixed", empty&#61;" ")fixed &#61; r.parameter("testing").map(methodcaller("strip")).filter(len).map(methodcaller("upper")).get_or("")fn.monad.Option.or_call是个便利的方法&#xff0c;它允许你进行多次调用尝试以完成计算。例如&#xff0c;你有一个Request类&#xff0c;它有type&#xff0c;mimetype和url等几个可选属性&#xff0c;你需要使用最少一个属性值以分析它的“request类型”&#xff1a;from fn.monad import Optionrequest &#61; dict(url&#61;"face.png", mimetype&#61;"PNG")tp &#61; Option \.from_value(request.get("type", None)) \ # check "type" key first.or_call(from_mimetype, request) \ # or.. check "mimetype" key.or_call(from_extension, request) \ # or... get "url" and check extension.get_or("application/undefined")其余事项&#xff1f;我仅仅描述了类库的一小部分&#xff0c;你还能够找到并使用以下功能&#xff1a;22个附加的itertools代码段&#xff0c;以扩展内置module的功能的附加功能将Python 2和Python 3的迭代器(iterator)(如range&#xff0c;map及filtter等等)使用进行了统一&#xff0c;这对使用跨版本的类库时非常有用为函数式组合及partial函数应用提供了简便的语法为使用高阶函数(apply&#xff0c;flip等等)提供了附加的操作符正在进行中的工作自从在Github上发布这个类库以来&#xff0c;我从社区中收到了许多审校观点、意见和建议&#xff0c;以及补丁和修复。我也在继续增强现有功能&#xff0c;并提供新的特性。近期的路线图包括以下内容&#xff1a;为使用可迭代对象(iterable)&#xff0c;如foldl&#xff0c;foldr增加更多操作符更多的monad&#xff0c;如fn.monad.Either&#xff0c;以处理错误记录为大多数module提供C-accelerator为简化lambda arg1: lambda arg2:…形式而提供的curry函数的生成器更多文档&#xff0c;更多测试&#xff0c;更多示例代码本文原创发布php中文网&#xff0c;转载请注明出处&#xff0c;感谢您的尊重&#xff01;
assert list(take(10, fib)) &#61;&#61; [0,1,1,2,3,5,8,13,21,34]
assert fib[20] &#61;&#61; 6765
assert list(fib[30:35]) &#61;&#61; [832040,1346269,2178309,3524578,5702887]
蹦床(Trampolines)修饰符
fn.recur.tco是一个不需要大量栈空间分配就可以处理TCO的临时方案。让我们先从一个递归阶乘计算示例开始&#xff1a;
def fact(n):
if n &#61;&#61; 0: return 1
return n * fact(n-1)
这种方式也能工作&#xff0c;但实现非常糟糕。为什么呢&#xff1f;因为它会递归式地保存之前的计算值以算出最终结果&#xff0c;因此消耗了大量的存储空间。如果你对一个很大的n值(超过了sys.getrecursionlimit()的值)执行这个函数&#xff0c;CPython就会以此方式失败中止&#xff1a;
>>> import sys
>>> fact(sys.getrecursionlimit() * 2)
... many many lines of stacktrace ...
RuntimeError: maximum recursion depth exceeded
这也是件好事&#xff0c;至少它避免了在你的代码中产生严重错误。
我们如何优化这个方案呢&#xff1f;答案很简单&#xff0c;只需改变函数以使用尾递归即可&#xff1a;
def fact(n, acc&#61;1):
if n &#61;&#61; 0: return acc
return fact(n-1, acc*n)
为什么这种方式更佳呢&#xff1f;因为你不需要保留之前的值以计算出最终结果。可以在Wikipedia上查看更多尾递归调用优化的内容。可是……Python的解释器会用和之前函数相同的方式执行这段函数&#xff0c;结果是你没得到任何优化。
fn.recur.tco为你提供了一种机制&#xff0c;使你可以使用“蹦床”方式获得一定的尾递归优化。同样的方式也使用在诸如Clojure语言中&#xff0c;主要思路是将函数调用序列转换为while循环。
from fn import recur
&#64;recur.tco
if n &#61;&#61; 0: return False, acc
return True, (n-1, acc*n)
&#64;recur.tco是一个修饰符&#xff0c;能将你的函数执行转为while循环并检验其输出内容&#xff1a;
(False, result)代表运行完毕
(True, args, kwargs)代表我们要继续调用函数并传递不同的参数
(func, args, kwargs)代表在while循环中切换要执行的函数
函数式风格的错误处理
假设你有一个Request类&#xff0c;可以按照传入其中的参数名称得到对应的值。要想让其返回值格式为全大写、非空并且去除头尾空格的字符串&#xff0c;你需要这样写&#xff1a;
class Request(dict):
def parameter(self, name):
return self.get(name, None)
r &#61; Request(testing&#61;"Fixed", empty&#61;" ")
param &#61; r.parameter("testing")
if param is None:
fixed &#61; ""
else:
param &#61; param.strip()
if len(param) &#61;&#61; 0:
fixed &#61; param.upper()
额&#xff0c;看上去有些古怪。用fn.monad.Option来修改你的代码吧&#xff0c;它代表了可选值&#xff0c;每个Option实例可代表一个Full或者Empty(这点也受到了Scala中Option的启发)。它为你编写长运算序列提供了简便的方法&#xff0c;并且去掉除了许多if/else语句块。
from operator import methodcaller
from fn.monad import optionable
&#64;optionable
fixed &#61; r.parameter("testing")
.map(methodcaller("strip"))
.filter(len)
.map(methodcaller("upper"))
.get_or("")
fn.monad.Option.or_call是个便利的方法&#xff0c;它允许你进行多次调用尝试以完成计算。例如&#xff0c;你有一个Request类&#xff0c;它有type&#xff0c;mimetype和url等几个可选属性&#xff0c;你需要使用最少一个属性值以分析它的“request类型”&#xff1a;
from fn.monad import Option
request &#61; dict(url&#61;"face.png", mimetype&#61;"PNG")
tp &#61; Option \
.from_value(request.get("type", None)) \ # check "type" key first
.or_call(from_mimetype, request) \ # or.. check "mimetype" key
.or_call(from_extension, request) \ # or... get "url" and check extension
.get_or("application/undefined")
其余事项&#xff1f;
我仅仅描述了类库的一小部分&#xff0c;你还能够找到并使用以下功能&#xff1a;
22个附加的itertools代码段&#xff0c;以扩展内置module的功能的附加功能
将Python 2和Python 3的迭代器(iterator)(如range&#xff0c;map及filtter等等)使用进行了统一&#xff0c;这对使用跨版本的类库时非常有用
为函数式组合及partial函数应用提供了简便的语法
为使用高阶函数(apply&#xff0c;flip等等)提供了附加的操作符
正在进行中的工作
自从在Github上发布这个类库以来&#xff0c;我从社区中收到了许多审校观点、意见和建议&#xff0c;以及补丁和修复。我也在继续增强现有功能&#xff0c;并提供新的特性。近期的路线图包括以下内容&#xff1a;
为使用可迭代对象(iterable)&#xff0c;如foldl&#xff0c;foldr增加更多操作符
更多的monad&#xff0c;如fn.monad.Either&#xff0c;以处理错误记录
为大多数module提供C-accelerator
为简化lambda arg1: lambda arg2:…形式而提供的curry函数的生成器
更多文档&#xff0c;更多测试&#xff0c;更多示例代码
本文原创发布php中文网&#xff0c;转载请注明出处&#xff0c;感谢您的尊重&#xff01;