可爱的 Python: 在 Python 中进行函数编程,第 3 部分
时间:2007-01-14 来源:linxh
一个永不满足于部分解决方案的读者 -- Richard Davies -- 提出了我们是否能够始终把绑定移到单独的表达式中这样一个问题。让我们快速地浏览一下我们为什么想要这么做,并展示一个由 comp.lang.python 的投稿者提供的一个非常棒的方法。
让我们首先回顾一下 functional 模块中的 Bindings 类。通过使用那个类的属性,我们能够确保一个特殊名称在给定块的范围内只有一个意思:
清单 1:带有受保护重绑定的 Python FP 部分
>>> from functional import * >>> let = Bindings() >>> let.car = lambda lst: lst[0] >>> let.car = lambda lst: lst[2] Traceback (innermost last): File "<stdin>", line 1, in ? File "d:\tools\functional.py", line 976, in __setattr__ raise BindingError, "Binding '%s' cannot be modified." % name functional.BindingError: Binding 'car' cannot be modified. >>> let.car(range(10)) 0 |
Bindings 类在一个模块或者函数 def 的范围之内完成我们想要的功能,但是在一个单独的表达式内无法使其工作。然而,在 ML-系列语言中,就能很自然地在单个表达式内创建绑定:
清单 2:Haskell 表达式级别的名称绑定
-- car (x:xs) = x -- *could* create module-level binding list_of_list = [[1,2,3],[4,5,6],[7,8,9]] -- 'where' clause for expression-level binding firsts1 = [car x | x <- list_of_list] where car (x:xs) = x -- 'let' clause for expression-level binding firsts2 = let car (x:xs) = x in [car x | x <- list_of_list] -- more idiomatic higher-order 'map' technique firsts3 = map car list_of_list where car (x:xs) = x -- Result: firsts1 == firsts2 == firsts3 == [1,4,7] |
Greg Ewing 注意到使用 Python 的列表理解可以达到同样的效果;我们甚至可以用象 Haskell 的语法那样清楚的方法来做:
清单 3:Python 2.0+ 表达式级别名称绑定
>>> list_of_list = [[1,2,3],[4,5,6],[7,8,9]] >>> [car_x for x in list_of_list for car_x in (x[0],)] [1, 4, 7] |
有个诀窍是把表达式放在列表理解的单项元组里,但是并不能提供任何使用表达层次绑定来处理高阶函数的方法。为了使用高阶函数,我们仍然需要使用块层次的绑定,如下所示:
清单 4:带有 'map()' 的 Python 块级别绑定
>>> list_of_list = [[1,2,3],[4,5,6],[7,8,9]] >>> let = Bindings() >>> let.car = lambda l: l[0] >>> map(let.car,list_of_list) [1, 4, 7]
|
不错,但是如果我们使用 map() ,绑定的范围仍旧比我们期望的要扩大了一点。虽然如此,即便在列表不是我们最终想使用的情况下,耐心地使用列表理解来为我们做名字绑定也是可能的:
清单 5:Python 列表理解的“逐级下降”
# Compare Haskell expression: # result = func car_car # where # car (x:xs) = x # car_car = car (car list_of_list) # func x = x + x^2 >>> [func for x in list_of_list ... for car in (x[0],) ... for func in (car+car**2,)][0] 2 |
我们已经完成了 list_of_list 中基于第一个元素的算术运算,而且同时命名了那个算术计算(但只是在表达式的范围内)。作为“优化”,我们大可不必去创建一个比开始的元素更长的列表,因为我们用最后的索引 0 来选择唯一的第一个元素:
清单 6:列表理解的有效逐级下降
>>> [func for x in list_of_list[:1] ... for car in (x[0],) ... for func in (car+car**2,)][0] 2 |
|
|
有三个最普通的高阶函数内建在 Python 中: map() 、 reduce() 和 filter() 。这些函数所做的 -- 也是我们称之为“高阶”的原因 -- 是把其他的函数作为他们的(一些)参数。其它的高阶函数(不是这些内建函数)返回函数对象。
Python 总是通过第一类函数对象的地位优势赋予使用者建造他们自己的高阶函数的能力。以下是一个小示例:
清单 7:小 Python 函数工厂
>>> def foo_factory (): ... def foo (): ... print "Foo function from factory" ... return foo ... >>> f = foo_factory() >>> f() Foo function from factory |
我在这个系列的 第 2 部分讨论过 Xoltar 工具包,它是带有高阶函数的非常好的集合。Xoltar 的 functional 模块提供的大多数函数是从各种传统的函数语言发展而来的,并且它们的有效性在这么多年已经被证实了。
可能最著名的和最重要的高阶的函数是 curry() 。 curry() 是以逻辑学家 Haskell Curry 命名的,他的名字也曾命名上面提到的编程语言。"currying" 优越的地方在于它能够象对待一个局部参数函数那样处理(几乎)每一个函数。所有对于 Curry 有必要做的就是容许函数的返回值对于它们自己也是函数,但是带有返回的函数可能是“狭窄”的或者“接近完成”的。这项工作和我所写的 第 2 部分有些相近 -- 每次连续的调用 curried 返回函数“充满”了在最后计算中的更多数据(数据依附于过程)。
让我们用 Haskell 的一个非常简单的示例来说明 Curry,然后在使用 functional 模块,在 Python 中重复使用同样的示例:
清单 8:Curry 一个 Haskell 计算
computation a b c d = (a + b^2+ c^3 + d^4) check = 1 + 2^2 + 3^3 + 5^4 fillOne = computation 1 -- specify "a" fillTwo = fillOne 2 -- specify "b" fillThree = fillTwo 3 -- specify "c" answer = fillThree 5 -- specify "d" -- Result: check == answer == 657 |
在 Python 中:
清单 9:Curry 一个 Python 计算
>>> from functional import curry >>> computation = lambda a,b,c,d: (a + b**2 + c**3 + d**4) >>> computation(1,2,3,5) 657 >>> fillZero = curry(computation) >>> fillOne = fillZero(1) # specify "a" >>> fillTwo = fillOne(2) # specify "b" >>> fillThree = fillTwo(3) # specify "c" >>> answer = fillThree(5) # specify "d" >>> answer 657 |
通过 第 2 部分(这次使用 curry() )中的税收计算程序的示例,可以进一步地和闭包进行比较:
清单 10:Python Curry 过的税收计算
from functional import * taxcalc = lambda income,rate,deduct: (income-(deduct))*rate taxCurry = curry(taxcalc) taxCurry = taxCurry(50000) taxCurry = taxCurry(0.30) taxCurry = taxCurry(10000) print "Curried taxes due =",taxCurry print "Curried expression taxes due =", \ curry(taxcalc)(50000)(0.30)(10000) |
不像闭包,我们需要用一个特定的顺序(从左向右)来排列参数。但是要注意到 functional 也包含一个开始于另一端(从右向左)的 curry() 类。
在同一级别示例的第二个 print 声明与直接调用普通的 taxcalc(50000,0.30,10000) 仅在拼写上做了少许的改变。然而,在不同的级别上,它使得每一个函数可以只有一个参数的函数这一概念更加清楚 -- 刚一接触这种思想的人可能会感到很惊讶。
|
|
在 curring 的“基本”操作之外, functional 提供了一个有趣的高阶函数的掠夺包。而且,写自己的高阶函数(带或者不带 functional ) 的确不难。至少带有 functional 的高阶函数会提供一些很好的主意。
对于大部分人来说,高阶函数感觉起来就像是标准的 map() 、 filter() 和 reduce() 的“增强”版本。通常,这些函数的模式是概略地“把一个或多个函数和一些列表作为参数,然后应用这些函数列出参数。”关于这个题目有数量惊人的有趣和有用的方法。另一个模式是“获取函数集合,并创建一个合并这些函数功能的函数。”再次,可能有众多的变化,让我们看一下 functional 提供的东西。函数 sequential() 和 also() 都能够创建一个基于组件函数序列的函数。组件函数可以用相同的参数来调用。两者主要的不同之处在于 sequential() 期望一个单一列表作为参数,而 also() 使用一列参数。在多数情况下,这些对于函数方面的影响是很有用的,但是 sequential() 随意地让您挑选哪个函数提供组合返回值:
清单 11:连续的函数调用(用相同的参数)
>>> def a(x): ... print x, ... return "a" ... >>> def b(x): ... print x*2, ... return "b" ... >>> def c(x): ... print x*3, ... return "c" ... >>> r = also(a,b,c) >>> r <functional.sequential instance at 0xb86ac> >>> r(5) 5 10 15 'a' >>> sequential([a,b,c],main=c)('x') x xx xxx 'c' |
在创建新功能(这些功能将参数应用于几个组件函数)方面,函数 disjoin() 和 conjoin() 与 sequential() 和 also() 相似。但是 disjoin() 询问是不是 任何组成函数都返回真(给定了参数),而且 conjoin() 询问是不是 所有的组件函数都返回真。在可能的地方应用逻辑快捷键,那么 disjoin() 可能就不会发生某些方面的影响。 joinfuncs() 与 also() 类似,它返回一个组件返回值的元组而不是选择一个主要返回值。
先前的函数让您访问具有相同参数的多个函数, any() 、 all() 和 none_of() 让您根据参数列表调用相同的函数。在通常的结构中,它们有一点象内建的 map() 、 reduce() 、 filter() 函数。但是这些来自于 functional 的特殊高阶函数询问的是关于返回值集合的布尔问题。例如:
清单 12:询问返回值集合
>>> from functional import * >>> isEven = lambda n: (n%2 == 0) >>> any([1,3,5,8], isEven) 1 >>> any([1,3,5,7], isEven) 0 >>> none_of([1,3,5,7], isEven) 1 >>> all([2,4,6,8], isEven) 1 >>> all([2,4,6,7], isEven) 0 |
对于那些有一点数学背景人来说,特别有意思的高阶函数是 compose() 。一些函数的组合是一个函数返回值的“联合链”,并输入下一个函数。编写这些函数的程序员应该负责确保输出和输入相匹配,但是这样的话,任何时候程序员都使用一个返回值。一个简单的示例就会让您明白:
清单 13: 创建组合函数
>>> def minus7(n): return n-7 ... >>> def times3(n): return n*3 ... >>> minus7(10) 3 >>> minustimes = compose(times3,minus7) >>> minustimes(10) 9 >>> times3(minus7(10)) 9 >>> timesminus = compose(minus7,times3) >>> timesminus(10) 23 >>> minus7(times3(10)) 23 |
|
|
我希望对高阶函数的最新研究能够唤起读者在一定的思考方式上的兴趣。请尽一切办法使用它。试着创建一些自己的高阶函数;有些可能是非常有用和非常强大的。请让我知道它是如何运行的;可能这个特别系列的后面部分将会讨论读者们继续提供的新奇的、迷人的方法。
|
|
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文.
- David 的 前一专栏( 第 1 部分和 第 2 部分),有关在 Python 中进行函数编程,是对本文的介绍。
- 函数编程的一个很好的起点就是经常访问 Frequently Asked Questions comp.lang.functional 。
- Bryn Keller 的 "xoltar toolkit",它包括了模块 functional ,添加了大量有用的对 Python 的 FP 扩展。因为 functional 模块本身完全是用 Python 编写的,所以它所做的在 Python 本身中已经可能存在。但 Keller 也指出了一组非常紧密集成的扩展,简洁定义中带有许多能力。
- 我发现通过语言 Haskell 比 Lisp/Scheme 更容易掌握函数编程(即使如果只在 Emacs 中,后者可能使用得更广泛)。其它 Python 程序员可能由于没有那么多的括号和前缀 (Polish) 操作符会轻松许多。
- 一本非常有用的介绍性书籍是 Haskell: The Craft of Functional Programming (第 2 版)、 Simon Thompson (Addison-Wesley, 1999).
- 一本介绍 Haskell 的同样优秀,感觉更侧重应用的书是: The Haskell School of Expression: Learning Functional Programming Through Multimedia, Paul Hudak (Cambridge University Press, 2000).
- 阅读 David 的 可爱的 Python 专栏中的其它文章:
- 在 Python 中开发纯文本检索
- 获取 2.0 版本
- 升级您的 Python 阅读列表
- JPython 内幕和用于.NET 的 Python
- 在 Python 中进行 TK 编程
- 快速重新装载
- Python 执行内幕
- 诅咒编程
- 在 Python 中进行文本处理
- 使用状态机
- 我的第一个基于 Web 的过滤代理
- DOM 的动态性
- 考虑 XML 和 Python