分享
 
 
 

Python中的函数编程-----刚开始涉足函数编程?

王朝other·作者佚名  2008-05-18
窄屏简体版  字體: |||超大  

专栏继续 David 对 Python 中的函数编程 (FP) 的介绍。请阅读本文对解决编程问题的不同范例的介绍,在介绍过程中 David 将阐述几个中高级的 FP 概念。

在第 1 部分,有关函数编程的前一个专栏中,我介绍了一些 FP 的基础概念。本专栏将进一步深入研究这一概念性相当强的领域。对于我们的研究,Bryn Keller 的 “Xoltar 工具包” 将提供有用的帮助。Keller 把很多 FP 的优势都集中在一个包括纯 Python 技术执行的小巧模块中。除了 functional 模块,Xoltar 工具包还包括 lazy 模块,该模块支持“只在需要时”赋值的结构。许多传统函数语言也具有缓式赋值,因此在这些组件中,Xoltar 工具包使您获得象 Haskell 这样的函数语言该有的大部分功能。

绑定

细心的读者可能记得我在第 1 部分的函数技术中指出的限制。特别在 Python 中不能避免表示函数表达式的名称的重新绑定。在 FP 中,名称通常被理解为较长表达式的缩写,但这一说法暗示着“同一表达式总是求出相同的值”。如果标记的名称重新被绑定,这一暗示便不成立。例如,让我们定义一些在函数编程中要用到的快捷表达式,比如:

清单 1. 以下 Python FP 部分的重新绑定要造成故障

>>> car = lambda lst: lst[0]

>>> cdr = lambda lst: lst[1:]

>>> sum2 = lambda lst: car(lst)+car(cdr(lst))

>>> sum2(range(10))

1

>>> car = lambda lst: lst[2]

>>> sum2(range(10))

5

不幸的是,完全相同的表达式 sum2(range(10)) 在程序中的两处求得两个不同的值,即使该表达式自身并没有在其参数中使用任何可变变量。

幸运的是,functional 模块提供了称为 Bindings 的类(向 Keller 提议)来防止这样的重新绑定(至少在偶然情况下,Python 不会阻止一心想要解除绑定的程序员)。然而使用 Bindings 需要一些额外的语法,这样意外就不太容易发生。在 functional 模块的示例中,Keller 将 Bindings 实例命名为 let (我假定在 ML 家族语言的 let 关键词的后面)。例如,我们会这样做:

清单 2. 具有安全重新绑定的 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.

>>> car(range(10))

0

很明显,真正的程序必须做一些设置来捕获“绑定错误”,而且他们被抛出也避免了一类问题的出现。

与 Bindings 一起,functional 提供 namespace 函数从 Bindings 实例中获取命名空间(实际上是个字典)。如果希望在 Bindings 中定义的(不可变)命名空间中运算一个表达式,这非常容易实现。Python 的 eval() 函数允许在命名空间中进行运算。让我们通过一个示例来弄清楚:

清单 3. 使用不可变命名空间的 Python FP 部分

>>> let = Bindings() # "Real world" function names

>>> let.r10 = range(10)

>>> let.car = lambda lst: lst[0]

>>> let.cdr = lambda lst: lst[1:]

>>> eval('car(r10)+car(cdr(r10))', namespace(let))

>>> inv = Bindings() # "Inverted list" function names

>>> inv.r10 = let.r10

>>> inv.car = lambda lst: lst[-1]

>>> inv.cdr = lambda lst: lst[:-1]

>>> eval('car(r10)+car(cdr(r10))', namespace(inv))

17

闭包

FP 中有个有趣的概念 -- 闭包。实际上,闭包对许多开发人员都非常有趣,即使在如 Perl 和 Ruby 这样的无函数语言中也都包括闭包这一功能。而且,Python 2.1 目前正想加入词汇范围限制功能,这一功能将提供闭包的大部分功能。

什么是闭包呢? Steve Majewski 最近在 Python 新闻组提供了对这一概念的很好描述:

对象是附带过程的数据……闭包是附带数据的过程。

闭包就象是 FP 的 Jekyll 对于 OOP 的 Hyde (角色或者也可能对调)。闭包类似对象示例,是一种将一大批数据和功能封装在一起的一种方式。

让我们回到先前的地方了解对象和闭包解决什么问题,同时了解一下问题如果没有这两样是如何解决的。函数返回的结果往往是由其计算中使用的上下文决定的。最常见的 -- 也可能是最明显的 -- 指定上下文的方法是向函数传递某些参数,通知函数处理什么值。但有时候“背景”和“前景”参数有着本质的区别 -- 在这特定时刻函数正在处理的和函数为多段潜在调用而“配置”之间的区别。

当把重点放在前景的时候,有许多处理背景的方法。其中一种是简单“咬出子弹”的方法,在每次调用的时候传递函数需要的每一个参数。这种方法通常在调用链中,只要在某些地方有可能需要值,就会传递一些值(或带有多成员的结构)。以下是一个小示例:

清单 4. 显示 cargo 变量的 Python 部分

>>> defa(n):

... add7 = b(n)

... return add7

...

>>> defb(n):

... i = 7

... j = c(i,n)

... return j

...

>>> defc(i,n):

... return i+n

...

>>> a(10) # Pass cargo value for use downstream

17

在 cargo 示例的 b() 中,n 除了起到传递到 c() 的作用外并无其他作用。另一种方法将使用全局变量:

清单 5. 显示全局变量的 Python 部分

>>> N = 10

>>> defaddN(i):

... global N

... return i+N

...

>>> addN(7) # Add global N to argument

17

>>> N = 20

>>> addN(6) # Add global N to argument

26

全局变量 N 在任何希望调用 addN() 的时候起作用,但没有必要明确地传递全局背景“上下文”。另一个更 Python 专用的技术是将一个变量在定义时“冻结”入一个使用默认参数的函数:

清单 6. 显示冻结变量的 Python 部分

>>> N = 10

>>> defaddN(i, n=N):

... return i+n

...

>>> addN(5) # Add 10

15

>>> N = 20

>>> addN(6) # Add 10 (current N doesn't matter)

16

冻结变量本质上就是闭包。某些数据被“隶属”于 addN() 函数。对于完整的闭包,当定义 addN() 的时候,所有的数据在调用的时候都将可用。然而,在这个示例(或者许多更健壮的示例)中,使用默认的参数就能简单的够用了。 addN() 从未使用的变量并不会对其计算造成影响。

接着让我们来看一个更接近真实问题的 OOP 方法。年份的时间是我想起了那些“会见”风格的收集各种数据的税收程序 -- 不必有特定的顺序 -- 最终使用全部数据来计算。让我们创建一个简单的版本:

清单 7. Python 风格的税收计算类/示例

class TaxCalc:

deftaxdue(self):

return (self.income-self.deduct)*self.rate

taxclass = TaxCalc()

taxclass.income = 50000

taxclass.rate = 0.30

taxclass.deduct = 10000

print "Pythonic OOP taxes due =", taxclass.taxdue()

在 TaxCalc 类(或其实例)中,能收集一些数据 -- 可以以任意顺序 -- 一旦获得了所需的所有元素,就能调用这一对象的方法来完成这一大批数据的计算。所有一切都在实例中,而且,不同示例携带不同的数据。创建多示例和区别它们的数据的可能性不可能存在于"全局变量"或"冻结变量"方法中。"cargo" 方法能处理这个问题,但对于扩展的示例来说,我们看到它可能是开始传递各种值的必要条件了。既然我们已讲到这,注意传递消息的 OPP 风格是如何处理的也非常有趣(Smalltalk 或 Self 与此类似,一些我使用的 OOP xBase 变量也是如此):

清单 8. Smalltalk 风格 (Python) 的税收计算

class TaxCalc:

deftaxdue(self):

return (self.income-self.deduct)*self.rate

defsetIncome(self,income):

self.income = income

return self

defsetDeduct(self,deduct):

self.deduct = deduct

return self

defsetRate(self,rate):

self.rate = rate

return self

print "Smalltalk-style taxes due =", TaxCalc().setIncome(50000).setRate(0.30).setDeduct(10000).taxdue()

用每个 "setter" 来返回 self 使我们能把“现有的”东西看作是每个方法应用的结果。这与 FP 闭包方法有许多有趣的相似点。

有了 Xoltar 工具包,我们就能创建具有所期望的合并数据与函数特性的完整的闭包,同时还允许多段闭包(nee 对象)来包含不同的包:

清单 9. Python 函数风格的税收计算

from functional import *

taxdue = lambda: (income-deduct)*rate

incomeClosure = lambda income,taxdue: closure(taxdue)

deductClosure = lambda deduct,taxdue: closure(taxdue)

rateClosure = lambda rate,taxdue: closure(taxdue)

taxFP = taxdue

taxFP = incomeClosure(50000,taxFP)

taxFP = rateClosure(0.30,taxFP)

taxFP = deductClosure(10000,taxFP)

print "Functional taxes due =",taxFP()

print "Lisp-style taxes due =", incomeClosure(50000,

rateClosure(0.30,

deductClosure(10000, taxdue)))()

我们定义的每一个闭包函数都携带了函数范围内定义的任何值,然后将这些值绑定到函数对象的全局范围。然而,函数的全局范围看上去不必与实际模块的全局范围相同,同时与不同闭包的“全局”范围也不相同。闭包只是简单地“携带数据”。

在示例中,我们使用了一些特殊函数在闭包范围 (income、deduct、rate) 内放入了特定绑定。修改设计以在范围内放入任何绑定也非常简单。我们还可以在示例中使用具有细微差别的不同函数风格,当然这只是为了好玩。第一个成功的将附加值绑定入闭包范围内;使 taxFP 成为可变,这些“加入到闭包”的行可以任意顺序出现。然而,如果要使用如 tax_with_Income 这样的不可变名称,就必须将绑定行按照一定顺序排列,然后将前面的绑定传递到下一个。无论如何,一旦必需的一切被绑定入闭包的范围内,我们就调用 "seeded" 函数。

第二种风格看上去更接近 Lisp,(对我来说更像圆括号)。如果不考虑美观,第二种风格中发生了二件有趣的事情。第一件是名称绑定完全被避免了。第二种风格是一个单一表达式而不使用语句(请参阅第 1 部分,讨论为什么这样会有问题)。

其它有关“Lisp 风格”闭包使用的有趣例子是其与上文提到的“Smalltalk 风格”消息传递方法有多少类似。两者累积了值和调用 taxdue() 函数/方法(如果没有正确的数据,两者在这些原始版本中都将报错)。“Smalltalk 风格”在每一步之间传递对象,而“Lisp 风格”传递一个连续。但若是更深一层理解,函数和面向对象编程大部分都是这样。

尾递归

在这一部分中,我们已经完成了函数编程领域的大部分内容。剩下的比以前(这节的题目是个小玩笑;可惜的是,它的概念在文章内没能解释)更少(可能更简单?)。阅读 functional 模块的代码是继续探索大量 FP 概念的好方法。这个模块有很多注释,并对其大部分函数和类提供了示例。本专栏没能涉及一些简化的元函数,它们使其它函数的组合和交互更容易处理。这些对于一个对函数范例不断探索的 Python 程序员来说非常值得研究。

参考资料

* David 的前一专栏,有关在 Python 进行函数编程,是对本文的介绍。

* Bryn Keller 的 "xoltar toolkit",它包括了模块 functional,添加了大量有用的对 Python 的 FP 扩展。因为 functional 模块本身完全是用 Python 编写的,所以它所做的在 Python 本身中已经可能存在。但 Keller 也指出了一组非常紧密集成的扩展,简洁定义中带有许多能力。

* Peter Norvig 撰写了一篇有意思的文章,Python for Lisp Programmers。但他的重点与我这一专栏的观点有些相反,它提供了 Python 和 Lisp 之间非常好的常规比较。

* comp.lang.functional 常见问题是了解函数编程的一个良好开始。

* 我发现用 Haskell 比用 Lisp/Scheme 更容易掌握函数编程(即使如果只在 Emacs 中,后者可能使用得更广泛)。其它 Python 程序员可能由于没有那么多的括号和前缀 (Polish) 操作符会轻松许多。

* 一本非常有用的介绍性书籍是 Haskell: The Craft of Functional Programming (2nd Edition), Simon Thompson (Addison-Wesley, 1999)。

关于作者

因为没有直觉的概念是空洞的,没有概念的直觉是盲目的,David Mertz 希望在他的办公室里放置弥尔顿的石膏像。现在他开始计划他的生日。可以通过 mertz@gnosis.cx 与 David 联系;在 http://gnosis.cx/publish/ 上详细介绍了他的生活。非常欢迎对过去的、这一篇或将来的专栏文章提出意见和建议。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有