可爱的 Python : Python中的函数式编程,第三部分
英文原文:Charming Python: Functional programming in Python, Part 3,翻译:开源中国
摘要: 作者David Mertz在其文章《可爱的Python:“Python中的函数式编程”》中的第一部分和第二部分中触及了函数式编程的大量基本概念。本文中他将继续前面的讨论,解释函数式编程的其它功能,如currying和Xoltar Toolkit中的其它一些高阶函数。
表达式绑定
有一位从不满足于解决部分问题读者,名叫Richard Davies,提出了一个问题,问是否可以将所有的绑定全部都转移到一个单个的表达式之中。首先让我们简单看看,我们为什么想这么做,然后再看看由comp.lang.python中的一位朋友提供的一种异常优雅地写表达式的方式。
让我们回想一下功能模块的绑定类。使用该类的特性,我们可以确认在一个给定的范围块内,一个特定的名字仅仅代表了一个唯一的事物。
具有重新绑定向导的 Python 函数式编程(FP)
|
1
2
3
4
5
6
7
8
9
10
11
12
|
>>> 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." % namefunctional.BindingError: Binding 'car' cannot be modified.>>> let.car(range(10))0 |
绑定类在一个模块或者一个功能定义范围内做这些我们希望的事情,但是没有办法在一条表达式内使之工作。然而在ML家族语言(译者注:ML是一种通用的函数式编程语言),在一条表达式内创建绑定是很自然的事。
Haskell 命名绑定表达式
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
-- car (x:xs) = x -- *could* create module-level bindinglist_of_list = [[1,2,3],[4,5,6],[7,8,9]]-- 'where' clause for expression-level bindingfirsts1 = [car x | x <- list_of_list] where car (x:xs) = x-- 'let' clause for expression-level bindingfirsts2 = let car (x:xs) = x in [car x | x <- list_of_list]-- more idiomatic higher-order 'map' techniquefirsts3 = map car list_of_list where car (x:xs) = x-- Result: firsts1 == firsts2 == firsts3 == [1,4,7] |
Greg Ewing 发现用Python的list概念实现同样的效果是有可能的;甚至我们可以用几乎与Haskell语法一样干净的方式做到。
Python 2.0+ 命名绑定表达式
|
1
2
3
4
|
>>> 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] |
在列表解析(list comprehension)中将表达式放入一个单项元素(a single-item tuple)中的这个小技巧,并不能为使用带有表达式级绑定的高阶函数提供任何思路。要使用这样的高阶函数,还是需要使用块级(block-level)绑定,就象以下所示:
Python中的使用块级绑定的’map()’
|
1
2
3
4
5
|
>>> 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(),那么其中的绑定范围可能会比我们想要的更宽一些。然而,我们可以做到的,哄骗列表解析让它替我们做名字绑定,即使其中的列表并不是我们最终想要得到的列表的情况下也没问题:
从Python的列表解析中“走下舞台”
|
1
2
3
4
5
6
7
8
9
10
11
12
|
# 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,所以我们仅仅选择的是第一个元素。:
从列表解析中高效地走下舞台
|
1
2
3
4
5
6
7
8
9
10
11
12
|
# 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 |
高阶函数:currying
Python内建的三个最常用的高阶函数是:map()、reduce()和filter()。这三个函数所做的事情 —— 以及谓之为“高阶”(higher-order)的原因 —— 是接受其它函数作为它们的(部分)参数。还有别的一些不属于内置的高阶函数,还会返回函数对象。
藉由函数对象在Python中具有首要地位, Python一直都有能让其使用者构造自己的高阶函数的能力。举个如下所示的小例子:
Python中一个简单函数工厂(function factory)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
>>> def foo_factory():... def foo():... print"Foo function from factory"... return foo...>>> f = foo_factory()>>> f()Foo function from factory |
本系列文章的第二部分我讨论过的Xoltar Toolkit中,有一组非常好用的高阶函数。Xoltar的functional模块中提供的绝大多数高阶函数都是在其它各种不同的传统型函数式编程语言中发展出来的高阶函数,其有用性已经过多年的实践验证。
可能其中最著名、最有用和最重要的高阶函数要数curry()了。函数curry()的名字取自于逻辑学家Haskell Curry,前文提及的一种编程语言也是用他姓名当中的名字部分命名的。”currying”背后隐含的意思是,(几乎)每一个函数都可以视为只带一个参数的部分函数(partial function)。要使currying能够用起来所需要做的就是让函数本身的返回值也是个函数,只不过所返回的函数“缩小了范围”或者是“更加接近完整的函数”。这和我在第二部分中提到的闭包特别相似 —— 对经过curry后的返回的后继函数进行调用时一步一步“填入”最后计算所需的更多数据(附加到一个过程(procedure)之上的数据)
现在让我们先用Haskell中一个很简单例子对curry进行讲解,然后在Python中使用functional模块重复展示一下这个简单的例子:
在Haskell计算中使用Curry
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
computation a b c d = (a + b^2+ c^3 + d^4)check = 1 + 2^2 + 3^3 + 5^4fillOne = computation 1-- specify "a"fillTwo = fillOne 2 -- specify "b"fillThree = fillTwo 3 -- specify "c"answer = fillThree 5 -- specify "d"-- Result: check == answer == 657 |
现在使用Python:
在Python计算中使用Curry
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
>>> 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">>> answer657 |
第二部分中提到过的一个简单的计税程序的例子,当时用的是闭包(这次使用curry()),可以用来进一步做个对比:
Python中curry后的计税程序
|
1
2
3
4
5
6
7
8
9
10
11
12
|
from functional import *taxcalc = lambda income,rate,deduct: (income-(deduct))*ratetaxCurry = curry(taxcalc)taxCurry = taxCurry(50000)taxCurry = taxCurry(0.30)taxCurry = taxCurry(10000)print "Curried taxes due =",taxCurryprint "Curried expression taxes due =", \ curry(taxcalc)(50000)(0.30)(10000) |
和使用闭包不同,我们需要以特定的顺序(从左到右)对参数进行curry处理。当要注意的是,functional模块中还包含一个rcurry()类,能够以相反的方向进行curry处理(从右到左)。
从一个层面讲,其中的第二个print语句同简单的同普通的taxcalc(50000,0.30,10000)函数调用相比只是个微小的拼写方面的变化。但从另一个不同的层面讲,它清晰地一个概念,那就是,每个函数都可以变换成仅仅带有一个参数的函数,这对于刚刚接触这个概念的人来讲,会有一种特别惊奇的感觉。
其它高阶函数
除了上述的curry功能,functional模块简直就是一个很有意思的高阶函数万能口袋。此外,无论用还是不用functional模块,编写你自己的高阶函数真的并不难。至少functional模块中的那些高阶函数为你提供了一些很值一看的思路。
它里面的其它高阶函数在很大程度上感觉有点象是“增强”版本的标准高阶函数map()、filter()和reduce()。这些函数的工作模式通常大致如此:将一个或多个函数以及一些列表作为参数接收进来,然后对这些列表参数运行它前面所接收到的函数。在这种工作模式方面,有非常大量很有意思也很有用的摆弄方法。还有一种模式是:拿到一组函数后,将这组函数的功能组合起来创建一个新函数。这种模式同样也有大量的变化形式。下面让我们看看functional模块里到底还有哪些其它的高阶函数。
sequential()和also()这两个函数都是在一系列成分函数(component function)的基础上创建一个新函数。然后这些成分函数可以通过使用相同的参数进行调用。两者的主要区别就在于,sequential()需要一个单个的函数列表作为参数,而also()接受的是一系列的多个参数。在多数情况下,对于函数的副作用而已这些会很有用,只是sequential()可以让你随意选择将哪个函数的返回值作为组合起来后的新函数的返回值。
顺序调用一系列函数(使用相同的参数)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
>>> 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' |
isjoin()和conjoin()这两个函数同equential()和also()在创建新函数并对参数进行多个成分函数的调用方面非常相似。只是disjoin()函数用来查询成分函数中是否有一个函数的返回值(针对给定的参数)为真;conjoin()函数用来查询是否所有的成分函数的返回值都为真。在这些函数中只要条件允许就会使用逻辑短路,因此disjoin()函数可能不会出现某些副作用。joinfuncs()i同also()类似,但它返回的是由所有成分函数的返回值组成的一个元组(tuple),而不是选中的某个主函数。
前文所述的几个函数让你可以使用相同的参数对一系列函数进行调用,而any()、all()和 none_of()这三个让你可以使用一个参数列表对同一个函数进行多次调用。在大的结构方面,这些函数同内置的map()、reduce()和filter()有点象。 但funtional模块中的这三个高阶函数中都是对一组返回值进行布尔(boolean)运算得到其返回值的。例如:
对一系列返回值的真、假情况进行判断
|
1
2
3
4
5
6
7
8
9
10
11
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 |
有点数学基础的人会对这个高阶函数非常感兴趣:iscompose(). 将多个函数进行合成(compostion)指的是,将一个函数的返回值同下个函数的输入“链接到一起”。对多个函数进行合成的程序员需要负责保证函数间的输入和输出是相互匹配的,不过这个条件无论是程序员在何时想使用返回值时都是需要满足的。举个简单的例子和阐明这一点:
创建合成函数
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
>>> 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 |
后会有期
衷心希望我对高阶函数的思考能够引起读者的兴趣。无论如何,请动手试一试。试着编写一些你自己的高阶函数;一些可能很有用,很强大。告诉我它如何运行;或许这个系列之后的章节会讨论读者不断提供的新观点,新想法。
文原文:Charming Python: Functional programming in Python, Part 3,翻译:开源中国
可爱的 Python : Python中的函数式编程,第三部分的更多相关文章
- Scala 中的函数式编程基础(三)
主要来自 Scala 语言发明人 Martin Odersky 教授的 Coursera 课程 <Functional Programming Principles in Scala>. ...
- Java中的函数式编程(三)lambda表达式
写在前面 lambda表达式是一个匿名函数.在Java 8中,它和函数式接口一起,共同构建了函数式编程的框架. lambda表达式乍看像是匿名内部类的一种语法糖,但实际上,它们是两种本质不同的事物 ...
- Java 中的函数式编程(Functional Programming):Lambda 初识
Java 8 发布带来的一个主要特性就是对函数式编程的支持. 而 Lambda 表达式就是一个新的并且很重要的一个概念. 它提供了一个简单并且很简洁的编码方式. 首先从几个简单的 Lambda 表达式 ...
- C#中的函数式编程:序言(一)
学了那么久的函数式编程语言,一直想写一些相关的文章.经过一段时间的考虑,我决定开这个坑. 至于为什么选择C#,在我看来,编程语言分三类:一类是难以进行函数式编程的语言,这类语言包括Java6.C语言等 ...
- (数据科学学习手札48)Scala中的函数式编程
一.简介 Scala作为一门函数式编程与面向对象完美结合的语言,函数式编程部分也有其独到之处,本文就将针对Scala中关于函数式编程的一些常用基本内容进行介绍: 二.在Scala中定义函数 2.1 定 ...
- C#中面向对象编程中的函数式编程详解
介绍 使用函数式编程来丰富面向对象编程的想法是陈旧的.将函数编程功能添加到面向对象的语言中会带来面向对象编程设计的好处. 一些旧的和不太老的语言,具有函数式编程和面向对象的编程: 例如,Smallta ...
- Apache Beam中的函数式编程理念
不多说,直接上干货! Apache Beam中的函数式编程理念 Apache Beam的编程范式借鉴了函数式编程的概念,从工程和实现角度向命令式妥协. 编程的领域里有三大流派:函数式.命令式.逻辑式. ...
- C#中的函数式编程:递归与纯函数(二) 学习ASP.NET Core Razor 编程系列四——Asp.Net Core Razor列表模板页面
C#中的函数式编程:递归与纯函数(二) 在序言中,我们提到函数式编程的两大特征:无副作用.函数是第一公民.现在,我们先来深入第一个特征:无副作用. 无副作用是通过引用透明(Referential ...
- 转:JavaScript函数式编程(三)
转:JavaScript函数式编程(三) 作者: Stark伟 这是完结篇了. 在第二篇文章里,我们介绍了 Maybe.Either.IO 等几种常见的 Functor,或许很多看完第二篇文章的人都会 ...
随机推荐
- php 格式
$abc = ($_POST[' : strtotime($_POST['start_time']); 解析:判断接收的数据是否为0,如果等于0赋值0,若不等于,则赋值获取的数值. strtotime ...
- windows内存管理方式以及优缺点
Windows内存管理方式:页式管理,段式管理,段页式管理 页式管理 将各进程的虚拟空间(逻辑地址)划分为若干个长度相等的页,业内管理把内存空间(物理内存)按照页的大小划分为片或者页面,从而实现了离散 ...
- nginx安装及配置支持php的教程(全)
本文的实验环境为:Centos4.5,nginx版本为:nginx-0.7.26 pcre-7.8.tar.gz 正则表达式下载地址:ftp://ftp.csx.cam.ac.uk/pub/so ...
- js设计模式
http://www.csdn.net/article/2011-09-02/303983 阐明JavaScript设计模式.CSDN研发频道对此文进行了整理选取部分内容,供开发者学习.参考. 内容如 ...
- perl /m 当作多行处理
高级用法 多行匹配: zjtest7-frontend:/root/0825# cat a2.pl print "1111111111111\n"; my $_="abc ...
- OpenLayers实现覆盖物选择信息提示
var map; function init() { map = new OpenLayers.Map("map",{projection:"EPSG:3857" ...
- poj 2425 A Chess Game_sg函数
题意:给你一个有向无环图,再给你图上的棋子,每人每次只能移动一个棋子,当轮到你不能移动棋子是就输了,棋子可以同时在一个点 比赛时就差这题没ak,做了几天博弈终于搞懂了. #include <io ...
- phpadmin
一晚上都在调试数据库,都要疯了,整理如下: 0.Apache服务器的443端口与VMware的冲突,所以要更改配置文件.设为440就可以(这个随意). 1.因为要远程访问,默认密码为空,所以首先给ro ...
- Time.deltaTime和Time.realtimeSinceStartup
private float f = 0f;void Update () { f += Time.deltaTime; Debug.LogError ("Time.delt ...
- stage.width/height和stage.stageWidth/stageHeight的区别
stage.stageWidth和stage.stageHeight就是舞台的宽带和高度 一般默认打开宽带是550,高度是400 那么stage.stageWidth=550,stage.stageH ...