Fn.py:享受Python中的函数式编程

尽管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(_ \u0026lt; 10, [9,10,11])) == [9]\
\

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

\
\from fn import _ \\print (_ + 2) # \"(x1) =\u0026gt; (x1 + 2)\" \print (_ + _ * _) # \"(x1, x2, x3) =\u0026gt; (x1 + (x2 * x3))\"\
\

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

\

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

\

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

\

Haskell

\
\fibs = 0 : 1 : zipWith (+) fibs (tail fibs)\
\

Clojure

\
\(def fib (lazy-cat [0 1] (map + fib (rest fib))))\
\

Scala

\
\def fibs: Stream[Int] = \     0 #:: 1 #:: fibs.zip(fibs.tail).map{case (a,b) =\u0026gt; a + b} \
\

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

\
\from fn import Stream \from fn.iters import take, drop, map\from operator import add\\f = Stream()\fib = f \u0026lt;\u0026lt; [0, 1] \u0026lt;\u0026lt; map(add, f, drop(1, f))\\assert list(take(10, fib)) == [0,1,1,2,3,5,8,13,21,34]\assert fib[20] == 6765\assert list(fib[30:35]) == [832040,1346269,2178309,3524578,5702887]\
\

蹦床(Trampolines)修饰符

\

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

\
\def fact(n):\     if n == 0: return 1\     return n * fact(n-1)\
\

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

\
\\u0026gt;\u0026gt;\u0026gt; import sys\\u0026gt;\u0026gt;\u0026gt; fact(sys.getrecursionlimit() * 2)\... many many lines of stacktrace ...\RuntimeError: maximum recursion depth exceeded\
\

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

\

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

\
\def fact(n, acc=1):\     if n == 0: return acc\     return fact(n-1, acc*n)\
\

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

\

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

\
\from fn import recur\\@recur.tco \def fact(n, acc=1):\     if n == 0: return False, acc\     return True, (n-1, acc*n)\
\

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

\
  • (False, result)代表运行完毕\
  • (True, args, kwargs)代表我们要继续调用函数并传递不同的参数\
  • (func, args, kwargs)代表在while循环中切换要执行的函数\

函数式风格的错误处理

\

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

\
\class Request(dict):\     def parameter(self, name):\         return self.get(name, None)\\r = Request(testing=\"Fixed\
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值