Python函数式编程

工作的项目主要是以Python进行开发的,经常会看到项目代码中的lambda表达式还有一些把函数作为参数或放在集合中的用法。之前只是浅显的知道是函数式编程,这里好好研究一下。下面从以下几个部分进行学习:

  • 什么是函数式编程?
  • 函数式编程的特点?
  • 函数式编程的用途?
  • Python中的函数式编程

什么是函数式编程?

简单说,"函数式编程"是一种"编程范式"(programming paradigm),也就是如何编写程序的方法论。

它属于"结构化编程"的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。

函数式编程关心类型(代数结构)之间的关系,是一种抽象程度很高的编程范式;
而命令式编程则关心解决问题的步骤。

函数式编程特点

函数式编程具有五个鲜明的特点。

  1. 函数是"第一等公民"

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

举例来说,下面代码中的print变量就是一个函数,可以作为另一个函数的参数。

var print = function(i){ console.log(i);};
[1,2,3].forEach(print);
  1. 只用"表达式",不用"语句"

"表达式"(expression)是一个单纯的运算过程,总是有返回值;"语句"(statement)是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。

原因是函数式编程的开发动机,一开始就是为了处理运算(computation),不考虑系统的读写(I/O)。"语句"属于对系统的读写操作,所以就被排斥在外。

3. 没有"副作用"

所谓"副作用"(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。

函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。

  1. 不修改状态

上一点已经提到,函数式编程只是返回新的值,不修改系统变量。因此,不修改变量,也是它的一个重要特点。

5. 引用透明

引用透明(Referential transparency),指的是函数的运行不依赖于外部变量或"状态",只依赖于输入的参数,任何时候只要参数相同,引用函数所得到的返回值总是相同的。

函数式编程的作用

  1. 代码简洁,开发快速
  2. 接近自然语言,易于理解
  3. 更方便的代码管理
  4. 易于"并发编程"

Python中的函数式编程

在Python中,函数式编程主要通过使用:

  1. 高阶函数
  2. 返回函数
  3. lambda函数
  4. 装饰器
  5. 偏函数

高阶函数

一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

例如,

def add(x, y, f):
    return f(x) + f(y)

print(add(-5, 6, abs))

Python中还内置了三种常用的高阶函数:

  • map/reduce
>>> def f(x):
...     return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

>>> from functools import reduce
>>> def add(x, y):
...     return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25
  • filter
def is_odd(n):
    return n % 2 == 1

list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]
  • sorted
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]

返回函数

返回求和函数

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum

闭包:一个函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行

def outer_func():
    loc_list = []
    def inner_func(name):
        loc_list.append(len(loc_list) + 1)
        print '%s loc_list = %s' %(name, loc_list)
    return inner_func

在这个例子中我们至少可以对闭包中引用的自由变量有如下的认识:

  • 闭包中的引用的自由变量只和具体的闭包有关联,闭包的每个实例引用的自由变量互不干扰。
  • 一个闭包实例对其自由变量的修改会被传递到下一次该闭包实例的调用。

lambda函数

  1. 一个语法

在Python中,lambda的语法是唯一的。其形式如下:
lambda argument_list: expression

其中,lambda是Python预留的关键字,argument_list和expression由用户自定义。这里的argument_list是参数列表,expression是一个关于参数的表达式。
这里的lambda argument_list: expression表示的是一个函数。这个函数叫做lambda函数。

  1. 三个特性

lambda函数有如下特性:

  • lambda函数是匿名的:所谓匿名函数,通俗地说就是没有名字的函数。lambda函数没有名字。
  • lambda函数有输入和输出:输入是传入到参数列表argument_list的值,输出是根据表达式expression计算得到的值。
  • lambda函数一般功能简单:单行expression决定了lambda函数不可能完成复杂的逻辑,只能完成非常简单的功能。由于其实现的功能一目了然,甚至不需要专门的名字来说明。

下面是一些lambda函数示例:

  • lambda x, y: xy;函数输入是x和y,输出是它们的积xy
  • lambda:None;函数没有输入参数,输出是None
  • lambda *args: sum(args); 输入是任意个数的参数,输出是它们的和(隐性要求是输入参数必须能够进行加法运算)
  • lambda **kwargs: 1;输入是任意键值对参数,输出是1
  1. 四个用法

由于lambda语法是固定的,其本质上只有一种用法,那就是定义一个lambda函数。在实际中,根据这个lambda函数应用场景的不同,可以将lambda函数的用法扩展为以下几种:

  • 将lambda函数赋值给一个变量,通过这个变量间接调用该lambda函数。
    例如,执行语句add=lambda x, y: x+y,定义了加法函数lambda x, y: x+y,并将其赋值给变量add,这样变量add便成为具有加法功能的函数。例如,执行add(1,2),输出为3。
  • 将lambda函数赋值给其他函数,从而将其他函数用该lambda函数替换。
    例如,为了把标准库time中的函数sleep的功能屏蔽(Mock),我们可以在程序初始化时调用:time.sleep=lambda x:None。这样,在后续代码中调用time库的sleep函数将不会执行原有的功能。例如,执行time.sleep(3)时,程序不会休眠3秒钟,而是什么都不做。
  • 将lambda函数作为其他函数的返回值,返回给调用者。
    函数的返回值也可以是函数。例如return lambda x, y: x+y返回一个加法函数。这时,lambda函数实际上是定义在某个函数内部的函数,称之为嵌套函数,或者内部函数。对应的,将包含嵌套函数的函数称之为外部函数。内部函数能够访问外部函数的局部变量,这个特性是闭包(Closure)编程的基础,在这里我们不展开。
  • 将lambda函数作为参数传递给其他函数。

例如:

dim_met_list = sorted(dim_met_list, key=lambda d: (int(d["map_type"]), 
int(d["dim_met_order"])))
list(map(lambda x: DATA_SET_PREFIX + x, DATA_SET_FIELD_SET))
  1. 一个争议
  • 支持方认为使用lambda编写的代码更紧凑,更“pythonic”。
  • 反对方认为,lambda函数能够支持的功能十分有限,其不支持多分支程序if...elif...else...和异常处理程序try ...except...。并且,lambda函数的功能被隐藏,对于编写代码之外的人员来说,理解lambda代码需要耗费一定的理解成本。

装饰器

一种常用的返回函数,特殊的闭包

偏函数

>>> int('12345')
12345
>>> int('12345', base=8)
5349
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85

总结

个人理解在一些数值计算和装饰器场景可以使用函数式编程使代码更简单,但是复杂的业务场景不建议使用函数式编程,会使代码难以理解,增加维护成本,降低开发效率。

参考文章:
https://www.liaoxuefeng.com/wiki/1016959663602400/1017328525009056

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

https://zhuanlan.zhihu.com/p/67978661

你可能感兴趣的:(Python函数式编程)