Python 函数编程
1. 乱谈函数编程
在大学里,老师教面向对象编程和面向过程编程的数不胜数,而函数编程却是少之又少。
其实函数编程的概念出现的比面向对象要早得多,按照百度百科的解释:函数式编程是种编程典范,它将电脑运算视为函数的计算。
两年前,我第一次听说函数编程的概念,记得当时的函数编程介绍会议上我被忽悠的一愣一愣的,当场下定决心学函数编程,这么高端的东西我都没有用过,简直羞于提起我是IT人员啊!当然,迫于各种现实问题,当时我还是没有去学习使用函数编程,好吧,羞愧一下····
近段时间开始学习python,发现python对于函数编程也有支持,于是乎理所当然的要捣鼓一下。
先讲讲我理解的函数编程(以下纯属个人理解,如果有错误,纯属正常) :
首先,函数编程必须做到的一点,函数的参数可以是函数,函数的返回值也可以是函数。(如果这都不能做到,还叫啥函数编程?)
其次,可以使用函数过程代替条件以及循环语句,条件语句很容易代替,部分循环语句就需要使用递归了。
最后,函数编程不能赋值。函数编程中最好没有变量,就算有也只是一个名称,而不是一个存储单元。
2. Python对函数编程的支持
好么,乱七八糟说了一通以后,回到正题,Python中是如何支持函数编程的呢?
按照在第一节中说的对于函数编程的理解来看,第一点,Python本身及支持函数的传递。第二点,Python中有以下几个函数:lambda、map、reduce、filter,加上Python对于条件判断的短路法则,完全可以做到。最后一点,则需要编码者自己约束与规范了。
2.1 函数传递
下面的代码使用了Python的函数传递(忘了交代了,我是用的是python 2.7):
1 def my_filter(list,rule_func): 2 result = [] 3 for item in list: 4 if rule_func(item): 5 result.append(item) 6 return result 7 8 def rule_positive(num): 9 if num>0: 10 return True 11 return False 12 13 def rule_len_noless_three(str): 14 if len(str)>=3: 15 return True 16 return False 17 18 if __name__ == '__main__': 19 list_int = [1,-8,2,6,-95,-88] 20 list_str = 'My name is Python'.split() 21 print (my_filter(list_int,rule_positive)) 22 print (my_filter(list_str,rule_len_noless_three))
这个代码试图完成一个过滤器,my_filter()函数的参数是一个列表与一个规则函数,my_filter()函数使用规则函数对列表中的元素进行筛选。通过例子,可以发现其实python对待函数就和面向对象编程语言中对待对象是一样的,直接把规则函数名当做对象传入my_filter()函数,在my_filter()函数中就可以直接使用规则函数了。就这样,python轻松的满足了函数编程的第一个要求。
2.2 lambda函数
百度百科在介绍函数编程的时候强调:函数编程语言最重要的基础是 λ 演算(lambda calculus)。而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。
既然python可以支持函数编程,理所当然的,python有一个lambda演算。
以2.1中的规则函数为例,将rule_positive()以及rule_len_noless_three()函数修改为同样功能的lambda演算:
1 rule_positive = lambda x:x > 0 2 rule_len_noless_three = lambda x:len(x) >= 3
更进一步,将my_filter()函数中的判断功能也用lambda演算代替(有人会认为多此一举,其实这里这么做的主要原因是可以清楚的看到lambda演算可以接受函数作为输入,当处理到一些很复杂的运算或数学推理时,用到这一点可能就会带来很大的方便):
1 judge_func = lambda n,f:f(n) 2 judge_func(-1,rule_positive)
从代码内容就可以直观的看出来这个lambda演算打算干什么,传入了一个值n和一个函数f,使用函数f对值n进行操作。
2.3 使用map、reduce、filter函数代替循环
在使用函数编程时,经常会需要一些短小精炼的函数去组织一些元素,就像人类语言中的谓词。一般面向对象编程时使用的for语句块、while循环以及if-else语句块都是属于用户自定义的“谓词”。值得庆幸的是,在Python中,已经提供了map(), reduce()以及filter()三个内嵌函数。
下面我们来看看如何使用这三个函数来代替循环语句块:
map()函数顾名思义是用来映射的,它使用传入的函数对列表的每个值进行操作,最后返回一个列表。
#不使用map()函数求平方
1 list = [1, 2, 3, 4] 2 result = [] 3 for item in list: 4 result.append(item * item) 5 print(result)
#使用map()函数
1 list = [1, 2, 3, 4] 2 print(map(lambda x:x*x, list))
上面两个代码片段做的是同样的事情,对于list中的每个值进行平方操作。这里的lambda演算直接写在map()函数中作为一个参数,这样做首先符合了我理解的函数编程的第三点——不赋值,还使得代码的可读性更高,这个map()到底做了什么一幕了然。
reduce()函数的中文解释是“减少”,它的作用是使用传入的函数对列表从头到尾进行一一操作,每次操作有两个数,操作的结果作为下一次操作的其中一个数,一直到列表结束,得到最后一个结果。也就是说在这里传入的函数必须是对两个数进行操作的函数。举个最简单易懂的例子,求10的阶层:
#不使用reduce()函数求10的阶层
1 result = 1 2 for num in range(1, 11): 3 result = result * num 4 print(result)
#使用reduce()函数
1 print(reduce(lambda x, y:x * y, range(1, 11)))
filter()函数就是用来过滤的,所以其实2.1中想做的过滤函数python已经有内嵌函数完成了,它使用一个返回bool值得函数来对列表中的值进行判断,留下通过判断的值,忽略通不过的,将2.1中的过滤函数使用filter()函数代替:
1 rule_positive = lambda x:x > 0 2 rule_len_noless_three = lambda x:len(x) >= 3 3 4 if __name__ == '__main__': 5 list_int = [1, -8, 2, 6, -95, -88] 6 list_str = 'My name is Python'.split() 7 print (filter(rule_positive, list_int)) 8 print (filter(rule_len_noless_three, list_str))
2.4 不赋值(避免副作用)
我看了很多资料,提到函数编程优点的时候大多都会提到“避免副作用”,这是一个很抽象的词语,我更喜欢直接把这个优点叫做“不赋值不出错”,直观易懂。如何理解“不赋值不出错”?
1 list = [3,5,8,23] 2 result = [] 3 for num in list: 4 tmp_num = num+1 5 #对tmp_num做一些别的操作 6 if tmp_num%2 == 0: 7 result.append(num) 8 print(result)
上面这段代码的主要功能是获得list中的奇数,但是就像我在代码中注释的,如果我想获取的不是奇数,而是对原始数据经过一系列操作后可以成为偶数的数,那么肯定需要更多对于变量tmp_num的操作,而这些操作就很有可能导致结果出错,而且tmp_num这个变量是零时性的,后继代码很可能根本需要知道它是什么。所以赋值就代表容易出现错误,这就是“赋值操作的副作用”。当然,避免这种“赋值副作用”的方法很简单,就是“不赋值”。
从多线程并行开发也很容易理解这一点。并行开发经常出现的同步问题,就是因为在并行语句块中存在着赋值语句,如果这些赋值语句不存在,同步的问题就根本不需要考虑。
既然写到这里,我们顺便把2.1的代码块变成没有赋值语句的代码吧:
1 if __name__ == '__main__': 2 print (filter(lambda x:x > 0, [1, -8, 2, 6, -95, -88])) 3 print (filter(lambda x:len(x) >= 3, 'My name is Python'.split()))
2.5 Python列表内嵌循环
其实这一点不应该是函数编程的,是python的一个特性,但是我觉得挺实用也挺有意思的。修改后的2.1代码:
1 if __name__ == '__main__': 2 list_int = [1, -8, 2, 6, -95, -88] 3 list_str = 'My name is Python'.split() 4 print ([x for x in list_int if x > 0]) 5 print ([x for x in list_str if len(x) >= 3])
对于那些本身使用过面向对象或者面向过程编程的人来说(比如我),这个看起来更直观,并且更愿意使用。
3. 结个尾
以上所诉,我自己看看都讲得乱起八糟,没有连贯性,但是已经是我的极其努力的结果了呀,哎╮(╯▽╰)╭
总的而言,python对函数编程的支持还是挺充分的,而且根据函数编程的特性来看,函数编程比较善于应用在多线程编程以及数学运算等方面。