Python的函数式编程

Contents

  1. 1. 目标
  2. 2. 命令式编程 & 函数式编程
  3. 3. Python函数式编程的基本单元
    1. 3.1. lambda
    2. 3.2. map()
    3. 3.3. reduce()
    4. 3.4. filter()
  4. 4. 替换条件控制语句
  5. 5. 替换循环控制语句
    1. 5.1. 替换for循环
    2. 5.2. 替换while循环
  6. 6. 更多示例
  7. 7. 总结
  8. 8. 参考链接

很早以前就听说过了函数式编程,印象中是一种很晦涩难懂的编程模式,但却一直没有去进行了解。

恰好这周组内的周会轮到我主持,一时也没想到要分享什么。灵光一闪,就选定函数式编程这个主题吧,反正组里的同事都没有学过,只需要讲解入门方面的知识就好,也正好可以借这个机会逼迫自己去学习下这种新的编程方式。

经过初步了解,发现支持函数式编程的语言挺多的,除了像Lisp、Scheme、Haskell、Erlang这样专用的函数式编程语言,我们常用的好多通用型编程语言(如Java、Python、Ruby、Javascript等)都支持函数式编程模式。考虑了下实际情况,最终还是选择Python作为函数式编程的入门语言,因为组内同事都熟悉Python,以此作为切入点不会产生太大困难。

经过查询资料和初步学习,对函数式编程有了些概念,经过整理,便形成了分享PPT。

以下便是这次分享的内容。

目标

通常,我们在新学习一门技术或者编程语言的时候,通常都会先从相关概念和特性入手。对于新接触函数式编程的人来说,可能会想知道如下几点:

  • 什么是函数式编程?
  • 函数式编程的特点?
  • 函数式编程的用途?
  • 函数式编程相比于命令式编程和面向对象编程的优缺点?

但是我这次分享却没有按照这个思路,因为我感觉在一开始就向听众灌输太多概念性的东西,反倒会让听众感到迷糊。因为经过查询资料发现,对于什么是函数化编程,很难能有一个协调一致的定义。而且由于我也是新接触,自身的理解可能会存在较大的偏差。

因此,我决定分享内容尽量从大家熟悉的命令式编程切入,通过大量实例来向听众展现函数式编程思维方式的不同之处。在这之后,再回过头看这几个问题,相信听众应该都会有更深刻的理解。

考虑到实际情况,本次分享希望能达成的目标是:

  • 了解函数式编程与命令式编程的主要区别
  • 掌握Python语言函数式编程的基本函数和算子
  • 会将简单的命令式编程语句转换为函数式编程

命令式编程 & 函数式编程

首先从大家熟悉的命令式编程开始,我们先回顾下平时在写代码时主要的情景。

其实,不管我们的业务代码有多复杂,都离不开以下几类操作:

  • 函数定义:def
  • 条件控制:if, elif, else
  • 循环控制:for, break, continue, while

当然,这只是部分操作类型,除此之外还应该有类和模块、异常处理等等。但考虑到是入门,我们就先只关注上面这三种最常见的操作。

对应地,函数式编程也有自己的关键字。在Python语言中,用于函数式编程的主要由3个基本函数和1个算子。

  • 基本函数:map()、reduce()、filter()
  • 算子(operator):lambda

令人惊讶的是,仅仅采用这几个函数和算子就基本上可以实现任意Python程序。

当然,能实现是一回事儿,实际编码时是否这么写又是另外一回事儿。估计要真只采用这几个基本单元来写所有代码的话,不管是在表达上还是在阅读上应该都挺别扭的。不过,尝试采用这几个基本单元来替代上述的函数定义、条件控制、循环控制等操作,对理解函数式编程如何通过函数和递归表达流程控制应该会很有帮助。

在开始尝试将命令式编程转换为函数式编程之前,我们还是需要先熟悉下这几个基本单元。

Python函数式编程的基本单元

lambda

lambda这个关键词在很多语言中都存在。简单地说,它可以实现函数创建的功能。

如下便是lambda的两种使用方式。


1

2

3


func1 = lambda : <expression()>

func2 = lambda x : <expression(x)>

func3 = lambda x,y : <expression(x,y)>

在第一条语句中,采用lambda创建了一个无参的函数func1。这和下面采用def创建函数的效果是相同的。


1

2


def func1():

<expression()>

在第二条和第三条语句中,分别采用lambda创建了需要传入1个参数的函数func2,以及传入2个参数的函数func3。这和下面采用def创建函数的效果是相同的。


1

2

3

4

5


def func2(x):

<expression(x)>

def func3(x,y):

<expression(x,y)>

需要注意的是,调用func1的时候,虽然不需要传入参数,但是必须要带有括号(),否则返回的只是函数的定义,而非函数执行的结果。这个和在ruby中调用无参函数时有所不同,希望ruby程序员引起注意。


1

2

3

4

5


>>> func = lambda : 123

>>> func

<function <lambda> at 0x100f4e1b8>

>>> func()

123

另外,虽然在上面例子中都将lambda创建的函数赋值给了一个函数名,但这并不是必须的。从下面的例子中大家可以看到,很多时候我们都是直接调用lambda创建的函数,而并没有命名一个函数,这也是我们常听说的匿名函数的由来。

map()

map()函数的常见调用形式如下所示:


1

map(func, iterable)

map()需要两个必填参数,第一个参数是一个函数名,第二个参数是一个可迭代的对象,如列表、元组等。

map()实现的功能很简单,就是将第二个参数(iterable)中的每一个元素分别传给第一个参数(func),依次执行函数得到结果,并将结果组成一个新的list对象后进行返回。返回结果永远都是一个list

简单示例如下:


1

2

3


>>> double_func = lambda s : s * 2

>>> map(double_func, [1,2,3,4,5])

[2, 4, 6, 8, 10]

除了传入一个可迭代对象这种常见的模式外,map()还支持传入多个可迭代对象。


1

map(func, iterable1, iterable2)

在传入多个可迭代对象的情况下,map()会依次从所有可迭代对象中依次取一个元素,组成一个元组列表,然后将元组依次传给func;若可迭代对象的长度不一致,则会以None进行补上。

通过以下示例应该就比较容易理解。


1

2

3

4

5

6

7


>>> plus = lambda x,y : (x or 0) + (y or 0)

>>> map(plus, [1,2,3], [4,5,6])

[5, 7, 9]

>>> map(plus, [1,2,3,4], [4,5,6])

[5, 7, 9, 4]

>>> map(plus, [1,2,3], [4,5,6,7])

[5, 7, 9, 7]

在上面的例子中,之所以采用x or 0的形式,是为了防止None + int出现异常。

需要注意的是,可迭代对象的个数应该与func的参数个数一致,否则就会出现异常,因为传参个数与函数参数个数不一致了,这个应该比较好理解。


1

2

3

4

5


>>> plus = lambda x,y : x + y

>>> map(plus, [1,2,3])

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

TypeError: <lambda>() takes exactly 2 arguments (1 given)

另外,map()还存在一种特殊情况,就是func为None。这个时候,map()仍然是从所有可迭代对象中依次取一个元素,组成一个元组列表,然后将这个元组列表作为结果进行返回。


1

2

3

4

5

6

7

8


>>> map(None, [1,2,3,4])

[1, 2, 3, 4]

>>> map(None, [1,2,3,4], [5,6,7,8])

[(1, 5), (2, 6), (3, 7), (4, 8)]

>>> map(None, [1,2,3,4], [5,6,7])

[(1, 5), (2, 6), (3, 7), (4, None)]

>>> map(None, [1,2,3,4], [6,7,8,9], [11,12])

[(1, 6, 11), (2, 7, 12), (3, 8, None), (4, 9, None)]

reduce()

reduce()函数的调用形式如下所示:


1

reduce(func, iterable[, initializer])

reduce()函数的功能是对可迭代对象(iterable)中的元素从左到右进行累计运算,最终得到一个数值。第三个参数initializer是初始数值,可以空置,空置为None时就从可迭代对象(iterable)的第二个元素开始,并将第一个元素作为之前的结果。

文字描述可能不大清楚,看下reduce()的源码应该就比较清晰了。


1

2

3

4

5

6

7

8

9

10

11


def reduce(function, iterable, initializer=None):

it = iter(iterable)

if initializer is None:

try:

initializer = next(it)

except StopIteration:

raise TypeError(‘reduce() of empty sequence with no initial value‘)

accum_value = initializer

for x in it:

accum_value = function(accum_value, x)

return accum_value

再加上如下示例,对reduce()的功能应该就能掌握了。


1

2

3

4

5


>>> plus = lambda x, y : x + y

>>> reduce(plus, [1,2,3,4,5])

15

>>> reduce(plus, [1,2,3,4,5], 10)

25

filter()

filter()函数的调用形式如下:


1

filter(func, iterable)

filter()有且仅有两个参数,第一个参数是一个函数名,第二个参数是一个可迭代的对象,如列表、元组等。

filter()函数的调用形式与map()比较相近,都是将第二个参数(iterable)中的每一个元素分别传给第一个参数(func),依次执行函数得到结果;差异在于,filter()会判断每次执行结果的bool值,并只将bool值为true的筛选出来,组成一个新的列表并进行返回。


1

2

3


>>> mode2 = lambda x : x % 2

>>> filter(mode2, [1,2,3,4,5,6,7,8,9,10])

[1, 3, 5, 7, 9]

以上便是Python函数式编程基本单元的核心内容。

接下来,我们就开始尝试采用新学习到的基本单元对命令式编程中的条件控制循环控制进行转换。

替换条件控制语句

在对条件控制进行替换之前,我们先来回顾下Python中对布尔表达式求值时进行的“短路”处理。

什么叫“短路”处理?简单地讲,就是如下两点:

  • f(x) and g(y)中,当f(x)false时,不会再执行g(y),直接返回false
  • f(x) or g(y)中,当f(x)true时,不会再执行g(y),直接返回true

结论是显然易现的,就不再过多解释。

那么,对应到条件控制语句,我们不难理解,如下条件控制语句和表达式是等价的。


1

2

3

4


# flow control statement

if <cond1>: func1()

elif <cond2>: func2()

else: func3()


1

2


# Equivalent "short circuit" expression

(<cond1> and func1()) or (<cond2> and func2()) or (func3())

通过这个等价替换,我们就去除掉了if/elif/else关键词,将条件控制语句转换为一个表达式。那这个表达式和函数式编程有什么关系呢?

这时我们回顾上面讲过的lambda,会发现lambda算子返回的就是一个表达式。

基于这一点,我们就可以采用lambda创建如下函数。


1

2

3

4

5

6

7

8

9

10


>>> pr = lambda s:s

>>> print_num = lambda x: (x==1 and pr("one")) \

....                 or (x==2 and pr("two")) \

....                 or (pr("other"))

>>> print_num(1)

‘one‘

>>> print_num(2)

‘two‘

>>> print_num(3)

‘other‘

通过函数调用的结果可以看到,以上函数实现的功能与之前的条件控制语句实现的功能完全相同。

到这里,我们就实现了命令式条件控制语句向函数式语句的转换。并且这个转换的方法是通用的,所有条件控制语句都可以采用这种方式转换为函数式语句。

替换循环控制语句

接下来我们再看循环控制语句的转换。在Python中,循环控制是通过forwhile这两种方式实现的。

替换for循环

for循环语句的替换十分简单,采用map()函数就能轻松实现。这主要是因为for语句和map()原理相同,都是对可迭代对象里面的每一个元素进行操作,因此转换过程比较自然。


1

2

3

4

5


# statement-based for loop

for e in lst: func(e)

# Equivalent map()-based loop

map(func, lst)


1

2

3

4

5

6

7

8

9

10


>>> square = lambda x : x * x

>>> for x in [1,2,3,4,5]: square(x)

...

1

4

9

16

25

>>> map(square, [1,2,3,4,5])

[1, 4, 9, 16, 25]

替换while循环

while循环语句的替换相比而言就复杂了许多。

下面分别是while循环语句及其对应的函数式风格的代码。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19


# statement-based while loop

while <condition>:

<pre-suite>

if <break_condition>:

break

else:

<suite>

# Equivalent FP-style recursive while loop

def while_block():

<pre-suite>

if <break_condition>:

return 1

else:

<suite>

return 0

while_FP = lambda: <condition> and (while_block() or while_FP())

while_FP()

这里的难点在于,函数式while_FP循环采用了递归的概念。当<condition>true时,进入循环体,执行while_block();若<break_condition>true时,返回1,while_FP()调用结束;若<break_condition>false时,返回0,会继续执行or右侧的while_FP(),从而实现递归调用;若<break_condition>始终为false,则会持续递归调用while_FP(),这就实现了while语句中同样的功能。

为了对函数式的while循环有更深刻的理解,可以再看下如下示例。这个例子是在网上找的,实现的是echo功能:输入任意非”quit”字符时,打印输入的字符;输入”quit”字符时,退出程序。


1

2

3

4

5

6

7

8

9

10

11

12


? PythonFP python pyecho.py

IMP -- 1

1

IMP -- 2

2

IMP -- abc

abc

IMP -- 1 + 1

1 + 1

IMP -- quit

quit

? PythonFP

如下便是分别采用过程式和函数式语句实现的”echo”功能。


1

2

3

4

5

6

7

8

9


# imperative version of "echo()"

def echo_IMP():

while 1:

x = raw_input("IMP -- ")

print x

if x == ‘quit‘:

break

echo_IMP()


1

2

3

4

5

6

7


def monadic_print(x):

print x

return x

# FP version of "echo()"

echo_FP = lambda: monadic_print(raw_input("FP -- "))==‘quit‘ or echo_FP()

echo_FP()

更多示例

到此为止,我们对函数式编程总算有了点认识,到达之前设定的目标应该是没有问题了,看来函数式编程也并没有想象中的那么难懂。

然而,这都只是函数式编程的皮毛而已,不信?再看下如下示例。

这个示例也是在网上找的,实现的是两个列表笛卡尔积的筛选功能,找出笛卡尔积元组集合中两个元素之积大于25的所有元组。


1

2

3

4

5

6

7


bigmuls = lambda xs,ys: filter(lambda (x,y):x*y > 25, combine(xs,ys))

combine = lambda xs,ys: map(None, xs*len(ys), dupelms(ys,len(xs)))

dupelms = lambda lst,n: reduce(lambda s,t:s+t, map(lambda l,n=n: [l]*n, lst))

print bigmuls([1,2,3,4],[10,15,3,22])

[(3, 10), (4, 10), (2, 15), (3, 15), (4, 15), (2, 22), (3, 22), (4, 22)]

虽然这个例子中lambda/map/reduce/filter都是我们已经比较熟悉了的基本单元,但是经过组合后,理解起来还是会比较吃力。

总结

看到这里,有的同学就开玩笑说我这标题名称非常贴切,《Python的函数式编程–从入门到?放弃?》,因为以后在工作中应该也不会再尝试使用函数式编程了,^_^。

不过,我还是觉得函数式编程挺有意思的,更高级的特性后面值得再继续学习。即使代码不用写成pure函数式风格,但在某些时候局部使用lambda/map/reduce/filter也能大大简化代码,也是一个不错的选择。

另外,通过此次分享,再次切身体会到了教授是最好的学习方式,只有当你真正能将一个概念讲解清楚的时候,你才算是掌握了这个概念。

参考链接

http://www.ibm.com/developerworks/linux/library/l-prog/index.html

原文地址:https://www.cnblogs.com/sddai/p/10105401.html

时间: 2024-10-28 03:47:11

Python的函数式编程的相关文章

python 入门 - 函数式编程

函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数 http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386819196283586a37629844456ca7e5a7faa9b94ee8000     函数 定义函数 def my_abs(x): if x >= 0: return x else: return -x result =my_

python之函数式编程

函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程 序设计.函数就是面向过程的程序设计的基本单元. 而函数式编程(请注意多了一个“式”字)——Functional Programming,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算. 我们首先要搞明白计算机(Computer)和计算(Compute)的概念. 在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和

python 之函数式编程

函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计.函数就是面向过程的程序设计的基本单元. 而函数式编程(请注意多了一个"式"字)--Functional Programming,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算. 我们首先要搞明白计算机(Computer)和计算(Compute)的概念. 在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条

Python的函数式编程,从入门到?放弃?

转:http://python.jobbole.com/84927/ 很早以前就听说过了函数式编程,印象中是一种很晦涩难懂的编程模式,但却一直没有去进行了解. 恰好这周组内的周会轮到我主持,一时也没想到要分享什么.灵光一闪,就选定函数式编程这个主题吧,反正组里的同事都没有学过,只需要讲解入门方面的知识就好,也正好可以借这个机会逼迫自己去学习下这种新的编程方式. 经过初步了解,发现支持函数式编程的语言挺多的,除了像Lisp.Scheme.Haskell.Erlang这样专用的函数式编程语言,我们常

python基础-函数式编程

python基础-函数式编程  高阶函数:map , reduce ,filter,sorted 匿名函数:  lambda  1.1函数式编程 面向过程编程:我们通过把大段代码拆成函数,通过一层一层的函数,可以把复杂的任务分解成简单的任务,这种一步一步的分解可以称之为面向过程的程序设计.函数就是面向过程的程序设计的基本单元. 函数式编程:是使用一系列函数去解决问题,函数式编程就是根据编程的范式来,得出想要的结果,只要是输入时确定的,输出就是确定的. 1.2高阶函数 能把函数作为参数传入,这样的

Python进阶-函数式编程

花了一个多小时学习python的函数式编程,终于算入门了,这接近一万字的教程,原作者分了十几课,我融合在一起方便查阅. 函数式编程 functional,是编程的一种范式 特点有: 把计算视为函数而不是指令 纯函数式编程不需要变量,没有副作用,测试简单 支持高阶函数,代码简洁 Python支持函数式编程: python不是纯函数式语言,允许有变量 支持高阶函数,函数也可作为变量传入 支持闭包,有了闭包就能返回函数 有限度地支持匿名函数 高阶函数是什么 先了解以下概念: 1.变量可以指向一个函数,

python之函数式编程与函数闭包

防伪码:忘情公子著 Python函数式编程: 函数式编程: 也称作泛函编程,是一种编程范型,说白了就是实现可以把函数当参数传递给另一个函数: 它将电脑运算视为数学上的函数计算,并且避免状态以及可变数据: 函数式编程语言最重要的基础是lambda演算,而且lambda演算的函数可以接受函数当作输入和输出 Python支持有限的函数式编程功能: filter(func,seq): 调用一个布尔函数func来迭代遍历每个seq中的元素:返回一个使func返回值为true的元素的序列 In [1]: d

python专题-函数式编程

函数式编程是使用一系列函数去解决问题,按照一般编程思维,面对问题时我们的思考方式是"怎么干",而函数函数式编程的思考方式是我要"干什么". 至于函数式编程的特点暂不总结,我们直接拿例子来体会什么是函数式编程. lambda表达式(匿名函数): 普通函数与匿名函数的定义方式: #普通函数 def add(a,b): return a + b print add(2,3) #匿名函数 add = lambda a,b : a + b print add(2,3) #==

Python进阶 函数式编程和面向对象编程等

函数式编程 函数:function 函数式:functional,一种编程范式.函数式编程是一种抽象计算机的编程模式. 函数!= 函数式(如计算!=计算机) 如下是不同语言的抽象 层次不同 高阶函数:能接收函数做参数的函数 变量可以指向函数 函数的参数可以接收变量 一个函数可以接收另一个函数作为参数 例子: 接收abs函数, 定义一个函数,接收x,y,z三个参数.其中x,y是数值,z是函数 . 1 2 3 def add(x,y,z):     return z(x)+z(y) print ad