python函数式编程

来源:互联网 发布:在线聊天 java 编辑:程序博客网 时间:2024/05/01 23:08
Reference:
1http://www.cnblogs.com/huxi/archive/2011/06/18/2084316.html
2、http://python.jobbole.com/81075/
3http://www.ruanyifeng.com/blog/2012/04/functional_programming.html
 什么是函数式编程?

函数式编程使用一系列的函数解决问题。函数仅接受输入并产生输出,不包含任何能影响产生输出的内部状态。任何情况下,使用相同的参数调用函数始终能产生同样的结果。

在一个函数式的程序中,输入的数据“流过”一系列的函数,每一个函数根据它的输入产生输出。函数式风格避免编写有“边界效应”(sideeffects)的函数:修改内部状态,或者是其他无法反应在输出上的变化。完全没有边界效应的函数被称为“纯函数式的”(purelyfunctional)。避免边界效应意味着不使用在程序运行时可变的数据结构,输出只依赖于输入。

可以认为函数式编程刚好站在了面向对象编程的对立面。对象通常包含内部状态(字段),和许多能修改这些状态的函数,程序则由不断修改状态构成;函数式编程则极力避免状态改动,并通过在函数间传递数据流进行工作。但这并不是说无法同时使用函数式编程和面向对象编程,事实上,复杂的系统一般会采用面向对象技术建模,但混合使用函数式风格还能让你额外享受函数式风格的优点。

函数式编程的特点是什么?
以下三点总结可大体判断某种编程是否属于函数式变成
(1、函数可以被传递、赋值给其他变量、做参数等,与其他数据类型有相同操作;
  2、函数要保持独立,不依赖于外部变量或"状态",只依赖于输入的参数,所有功能就是返回一个新的值,    没有其他行为,尤其是不得修改外部变量的值
  3、不修内部状态或者是无法反应在输出上的变化(有些状态改变,但这些改变没有反应在输出上,如一              些中间变量,所以函数内部少用变量,仅仅为了控制流程而定义的循环变量和流程中产生的临时变量              无疑是最需要避免的
1、函数是第一等公民

所谓"第一等公民"(firstclass),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。

例子:

定义一个函数

1
2
def add(x,y):
    return x+ y
1.1.使用函数赋值

使用def定义的函数也可以赋值,相当于为函数取了一个别名,并且可以使用这个别名调用函数:

1
2
add_a_number_to_another_one_by_using_plus_operator= add
print add_a_number_to_another_one_by_using_plus_operator(1,2)

1.2.作为参数

如果你对OOP的模板方法模式很熟悉,相信你能很快速地学会将函数当作参数传递。两者大体是一致的,只是在这里,我们传递的是函数本身而不再是实现了某个接口的对象

注意:此处的reduce_并不能算作函数式编程,它用了变量,还有for循环,但是此处的function是一个函数,是函数作为参数传递的例子
def reduce_(function,lst, initial):
    result= initial
    for numin lst:
        result= function(result,num)
    return result
print reduce_(add,lst,0)

现在,想要算出乘积,可以这样做:

print reduce_(lambda x,y: x * y,lst,1)

1.3.作为返回值

将函数返回通常需要与闭包一起使用(即返回一个闭包)才能发挥威力。

1:lst= map_(lambda x:add(10,x), lst)

2:lst= map_(add_to(10),lst)#add_to(10)返回一个函数,这个函数接受一个参数并加上10后返回

3:def add_to(n):
 return lambda x:add(n, x)
上面函数中lambda x:add(n, x)是一个闭包,它作为一个返回值,功能是结构一个参数并加上10,也就等同于1中的式子。

2、匿名函数(lambda) 

lambda提供了快速编写简单函数的能力。对于偶尔为之的行为,lambda让你不再需要在编码时跳转到其他位置去编写函数。 
lambda表达式定义一个匿名的函数,如果这个函数仅在编码的位置使用到,你可以现场定义、直接使用:

lst.sort(lambda o1,o2: o1.compareTo(o2))

关于匿名函数的使用场合问题:lambda的目的是为了编写偶尔为之的、简单的、可预见不会被修改的匿名函数。这种风格虽然看起来很酷,但并不是一个好主意,特别是当某一天需要对它进行扩充,再也无法用一个表达式写完时。如果一开始就需要给函数命名,应该始终使用def关键字。

3、封装控制结构的内置模板函数

为了避开边界效应,函数式风格尽量避免使用变量,而仅仅为了控制流程而定义的循环变量和流程中产生的临时变量无疑是最需要避免的。 
假如我们需要对刚才的数集进行过滤得到所有的正数,使用指令式风格的代码应该像是这样:

lst2= list()
for iin range(len(lst)):#模拟经典for循环
    if lst[i]>0:
        lst2.append(lst[i])

这段代码把从创建新列表、循环、取出元素、判断、添加至新列表的整个流程完整的展示了出来,俨然把解释器当成了需要手把手指导的傻瓜。然而,“过滤”这个动作是很常见的,为什么解释器不能掌握过滤的流程,而我们只需要告诉它过滤规则呢? 
在Python里,过滤由一个名为filter的内置函数实现。有了这个函数,解释器就学会了如何“过滤”,而我们只需要把规则告诉它:

lst2= filter(lambda n:n > 0,lst)

这个函数带来的好处不仅仅是少写了几行代码这么简单。 封装控制结构后,代码中就只需要描述功能而不是做法,这样的代码更清晰,更可读。因为避开了控制结构的干扰,第二段代码显然能让你更容易了解它的意图。 另外,因为避开了索引,使得代码中不太可能触发下标越界这种异常,除非你手动制造一个。 函数式编程语言通常封装了数个类似“过滤”这样的常见动作作为模板函数。唯一的缺点是这些函数需要少量的学习成本,但这绝对不能掩盖使用它们带来的好处。

 4、闭包(closure) (闭包是一个函数,一个函数定义在另一个函数的作用域中,并且函数中引用了外部函数的局部变量,那么这个函数就是一个闭包

闭包是绑定了外部作用域的变量(但不是全局变量)的函数。大部分情况下外部作用域指的是外部函数。 
闭包包含了自身函数体和所需外部函数中的“变量名的引用”。引用变量名意味着绑定的是变量名,而不是变量实际指向的对象;如果给变量重新赋值,闭包中能访问到的将是新的值。 
闭包使函数更加灵活和强大。即使程序运行至离开外部函数,如果闭包仍然可见,则被绑定的变量仍然有效;每次运行至外部函数,都会重新创建闭包,绑定的变量是不同的,不需要担心在旧的闭包中绑定的变量会被新的值覆盖。 

闭包是一类特殊的函数。如果一个函数定义在另一个函数的作用域中,并且函数中引用了外部函数的局部变量,那么这个函数就是一个闭包。下面的代码定义了一个闭包:

def f():
    n= 1
    def inner():
        print n
    inner()
    n= 'x'
    inner()

函数inner定义在f的作用域中,并且在inner中使用了f中的局部变量n,这就构成了一个闭包。闭包绑定了外部的变量,所以调用函数f的结果是打印1和'x'。这类似于普通的模块函数和模块中定义的全局变量的关系:修改外部变量能影响内部作用域中的值,而在内部作用域中定义同名变量则将遮蔽(隐藏)外部变量。

如果需要在函数中修改全局变量,可以使用关键字global修饰变量名。Python2.x中没有关键字为在闭包中修改外部变量提供支持,在3.x中,关键字nonlocal可以做到这一点:

#Python 3.x supports`nonlocal'
def f():
    n= 1
    def inner():
        nonlocaln
        n= 'x'
    print(n)
    inner()
    print(n)

调用这个函数的结果是打印1和'x',如果你有一个Python3.x的解释器,可以试着运行一下。

由于使用了函数体外定义的变量,看起来闭包似乎违反了函数式风格的规则即不依赖外部状态。但是由于闭包绑定的是外部函数的局部变量,而一旦离开外部函数作用域,这些局部变量将无法再从外部访问;另外闭包还有一个重要的特性,每次执行至闭包定义处时都会构造一个新的闭包,这个特性使得旧的闭包绑定的变量不会随第二次调用外部函数而更改。所以闭包实际上不会被外部状态影响,完全符合函数式风格的要求。(这里有一个特例,Python3.x中,如果同一个作用域中定义了两个闭包,由于可以修改外部变量,他们可以相互影响。)

虽然闭包只有在作为参数和返回值时才能发挥它的真正威力,但闭包的支持仍然大大提升了生产率。

     5内置的不可变数据结构 

为了避开边界效应,不可变的数据结构是函数式编程中不可或缺的部分。不可变的数据结构保证数据的一致性,极大地降低了排查问题的难度。 

例如,Python中的元组(tuple)就是不可变的,所有对元组的操作都不能改变元组的内容,所有试图修改元组内容的操作都会产生一个异常。 
函数式编程语言一般会提供数据结构的两种版本(可变和不可变),并推荐使用不可变的版本。
6、在其他类型的语言中,变量往往用来保存"状态"(state)。不修改变量,意味着状态不能保存在变量中。函数式编程使用参数保存状态,最好的例子就是递归。由于使用了递归,函数式语言的运行速度比较慢,这是它长期不能在业界推广的主要原因。
递归是另一种取代循环的方法。递归其实是函数式编程很常见的形式,经常可以在一些算法中见到。但之所以放到最后,是因为实际上我们一般很少用到递归。如果一个递归无法被编译器或解释器优化,很容易就会产生栈溢出;另一方面复杂的递归往往让人感觉迷惑,不如循环清晰,所以众多最佳实践均指出使用循环而非递归。 

python内建函数map、fliter、reduce、zip


一个完整的命令式转向函数式的编程实例

写声明式代码,而不是命令式

下面的程序演示三辆车比赛。每次移动时间,每辆车可能移动或者不动。每次移动时间程序会打印到目前为止所有车的路径。五次后,比赛结束

参看:http://www.ruanyifeng.com/blog/2012/04/functional_programming.html


0 0