支持函数式编程的语言通常具有如下特征,大量使用这些特征的代码即可被认为是函数式的:
函数是一等公民
函数能作为参数传递,或者是作为返回值返回。这个特性使得模板方法模式非常易于编写,这也促使了这个模式被更频繁地使用。
以一个简单的集合排序为例,假设lst是一个数集,并拥有一个排序方法sort需要将如何确定顺序作为参数。
如果函数不能作为参数,那么lst的sort方法只能接受普通对象作为参数。这样一来我们需要首先定义一个接口,然后定义一个实现该接口的类,最后将该类的一个实例传给sort方法,由sort调用这个实例的compare方法,就像这样:
#伪代码
interfaceComparator{
compare(o1,o2)
}
lst=list(range(5))
lst.sort(Comparator(){
compare(o1,o2){
returno2-o1//逆序
})
可见,我们定义了一个新的接口、新的类型(这里是一个匿名类),并new了一个新的对象只为了调用一个方法。如果这个方法可以直接作为参数传递会怎样呢?看起来应该像这样:
defcompare(o1,o2):
returno2-o1#逆序
lst=list(range(5))
lst.sort(compare)
请注意,前一段代码已经使用了匿名类技巧从而省下了不少代码,但仍然不如直接传递函数简单、自然。
匿名函数(lambda)
lambda提供了快速编写简单函数的能力。对于偶尔为之的行为,lambda让你不再需要在编码时跳转到其他位置去编写函数。
lambda表达式定义一个匿名的函数,如果这个函数仅在编码的位置使用到,你可以现场定义、直接使用:
1lst.sort(lambdao1,o2:o1.compareTo(o2))
相信从这个小小的例子你也能感受到强大的生产效率:)
封装控制结构的内置模板函数
为了避开边界效应,函数式风格尽量避免使用变量,而仅仅为了控制流程而定义的循环变量和流程中产生的临时变量无疑是最需要避免的。
假如我们需要对刚才的数集进行过滤得到所有的正数,使用指令式风格的代码应该像是这样:
lst2=list()
foriinrange(len(lst)):#模拟经典for循环
iflst[i]>0:
lst2.append(lst[i])
这段代码把从创建新列表、循环、取出元素、判断、添加至新列表的整个流程完整的展示了出来,俨然把解释器当成了需要手把手指导的傻瓜。然而,“过滤”这个动作是很常见的,为什么解释器不能掌握过滤的流程,而我们只需要告诉它过滤规则呢?
在Python里,过滤由一个名为filter的内置函数实现。有了这个函数,解释器就学会了如何“过滤”,而我们只需要把规则告诉它:
1lst2=filter(lambdan:n>0,lst)
这个函数带来的好处不仅仅是少写了几行代码这么简单。
封装控制结构后,代码中就只需要描述功能而不是做法,这样的代码更清晰,更可读。因为避开了控制结构的干扰,第二段代码显然能让你更容易了解它的意图。
另外,因为避开了索引,使得代码中不太可能触发下标越界这种异常,除非你手动制造一个。
函数式编程语言通常封装了数个类似“过滤”这样的常见动作作为模板函数。唯一的缺点是这些函数需要少量的学习成本,但这绝对不能掩盖使用它们带来的好处。
闭包(closure)
闭包是绑定了外部作用域的变量(但不是全局变量)的函数。大部分情况下外部作用域指的是外部函数。
闭包包含了自身函数体和所需外部函数中的“变量名的引用”。引用变量名意味着绑定的是变量名,而不是变量实际指向的对象;如果给变量重新赋值,闭包中能访问到的将是新的值。
闭包使函数更加灵活和强大。即使程序运行至离开外部函数,如果闭包仍然可见,则被绑定的变量仍然有效;每次运行至外部函数,都会重新创建闭包,绑定的变量是不同的,不需要担心在旧的闭包中绑定的变量会被新的值覆盖。
回到刚才过滤数集的例子。假设过滤条件中的0这个边界值不再是固定的,而是由用户控制。如果没有闭包,那么代码必须修改为:
classgreater_than_helper:
def__init__(self,minval):
self.minval=minval
defis_greater_than(self,val):
returnval>self.minval
defmy_filter(lst,minval):
helper=greater_than_helper(minval)
returnfilter(helper.is_greater_than,lst)
请注意我们现在已经为过滤功能编写了一个函数my_filter。如你所见,我们需要在别的地方(此例中是类greater_than_helper)持有另一个操作数minval。
如果支持闭包,因为闭包可以直接使用外部作用域的变量,我们就不再需要greater_than_helper了:
defmy_filter(lst,minval):
returnfilter(lambdan:n>minval,lst)
可见,闭包在不影响可读性的同时也省下了不少代码量。
函数式编程语言都提供了对闭包的不同程度的支持。在Python2.x中,闭包无法修改绑定变量的值,所有修改绑定变量的行为都被看成新建了一个同名的局部变量并将绑定变量隐藏。Python3.x中新加入了一个关键字nonlocal以支持修改绑定变量。但不管支持程度如何,你始终可以访问(读取)绑定变量。
内置的不可变数据结构
为了避开边界效应,不可变的数据结构是函数式编程中不可或缺的部分。不可变的数据结构保证数据的一致性,极大地降低了排查问题的难度。
例如,Python中的元组(tuple)就是不可变的,所有对元组的操作都不能改变元组的内容,所有试图修改元组内容的操作都会产生一个异常。
函数式编程语言一般会提供数据结构的两种版本(可变和不可变),并推荐使用不可变的版本。
递归
递归是另一种取代循环的方法。递归其实是函数式编程很常见的形式,经常可以在一些算法中见到。但之所以放到最后,是因为实际上我们一般很少用到递归。如果一个递归无法被编译器或解释器优化,很容易就会产生栈溢出;另一方面复杂的递归往往让人感觉迷惑,不如循环清晰,所以众多最佳实践均指出使用循环而非递归。
这一系列短文中都不会关注递归的使用。
以上内容为大家介绍了Python如何辨认函数式风格?希望对大家有所帮助,如果想要了解更多Python相关知识,请关注IT培训机构:千锋教育。