Python 函数式编程入门教程
函数式编程源自于数学理论,它似乎也更适用于数学计算相关的场景,因此本文以一个简单的数据处理问题为例,逐步介绍 Python 函数式编程从入门到走火入魔的过程。
一、什么是函数式编程?
函数:function
函数式:functional,是一种编程范式
二、函数式编程的特点:
1)把计算视为函数而非指令
2)纯函数式编程:不需要变量,没有副作用,测试简单
3)支持高阶函数,代码简洁
三、Python支持的函数式编程
1)Python不是纯函数式编程:允许有变量
2)Python支持高阶函数:函数也可以作为变量传入
3)Python支持闭包:有了闭包就能返回函数
4)有限度的支持你匿名函数
问题:计算 N 位同学在某份试卷的 M 道选择题上的得分(每道题目的分值不同)。
首先来生成一组用于计算的伪造数据:
四、入门
首先来看常规的面向过程编程风格,我们需要遍历每个学生,然后遍历每个学生对每道题目的答案并与真实答案进行比较,然后将正确答案的分数累计:
如果你觉得上面的代码非常直观且合乎逻辑,那说明你已经习惯按照计算机的思维模式进行思考了。通过创建嵌套两个 for 循环来遍历所有题目答案的判断和评分,这完全是为计算机服务的思路,虽然说 Python 中的 for 循环已经比 C 语言更进了一步,通常不需要额外的状态变量来记录当前循环的次数,但有时候也不得不使用状态变量,如上例中第二个循环中比较两个列表的元素。函数式编程的一大特点就是尽量抛弃这种明显循环遍历的做法,而是把注意集中在解决问题本身,一如在现实中我们批改试卷时,只需要将两组答案并列进行比较即可:
然后再将所有正确题目的分数累加起来,即可:
from functools import reduce
reduced = reduce(lambda x, y: x + y[1][1], filtered, 0)
print(reduced)
以上是对一位学生的结果处理,接下来只需要对所有学生进行同样的处理即可:
上面的示例通过 zip/filter/reduce/map 等函数将数据处理的方法打包应用到数据上,实现了基本的函数式编程操作。但是如果你对函数式有更深入的了解,你就会发现上面的 cal 方法中使用了全局变量 QUIZE,这会导致在相同输入的条件下,函数可能产生不同的输出,这是 FP 的大忌,因此需要进行整改:
如此借助闭包(Closure)的方法,就可以维持纯净的 FP 模式啦!
函数式编程的一大优势就是Immutable Data(数据不可变),就是不依赖于外部的数据,而且也不改变外部数据的值,这种思想可以大大减少我们代码的Bug,而且函数式编程也支持我们像使用变量一样使用函数。Python作为面向对象语言,也提供了对于函数式编程的支持,虽然并不是那么纯粹,而且也不支持尾递归优化。
1.lambda的使用
lambda即匿名函数,合理地使用lambda不仅可以减少我们的代码量,而且也可以更好地描绘代码逻辑,比如现在我们有下面这样一个函数。
>>>deff(x):
...returnx+x
# 调用这个函数
>>>f(2)
4
这个函数如果我们用lamda改写的话,只要一行代码就够了。# lambda后面的x表示lambda函数要接收的参数,x + x表示lambda函数所要返回的值
>>>f=lambdax:x+x
# 可以看到f现在也是一个函数对象
>>>f
>
# 调用lambda函数
>>>f(2)
4
python中map()函数
map(function, iterable)接收两个参数,第一个参数代表的是接收一个函数,第二个参数代表的是接收一个iteralbe类型的对象,比如list。
map函数的原理是:
1.每次从iterable中取出一个参数
2.将这个参数传递给我们的函数
3.然后函数返回的值加入一个list(这种说法不准确,只是为了帮助大家理解,后面我会解释)。等所有的iterable对象遍历完,map就把这个list返回给我们的调用者。下面我们直接通过实例来了解一下map的用法。
python中reduce()函数
reduce()函数也是Python内置的一个高阶函数。reduce()函数接收的参数和 map()类似,一个函数 f,一个list,但行为和 map()不同,reduce()传入的函数 f 必须接收两个参数,reduce()对list的每个元素反复调用函数f,并返回最终结果值。
例如:
请利用recude()来求积:
输入:[2, 4, 5, 7, 12]
输出:245712的结果
def prod(x, y):
return x*y
print reduce(prod, [2, 4, 5, 7, 12])
输出:3360
python中filter()函数
和map/reduce类似,filter(function, iterable)一次也接收两个参数,一个参数是函数,另外一个参数是iterable对象,从名字也可以看出,filter用于过滤iterble对象,比如说list(列表)。
它的原理是每次从iterable对象中取出一个元素作用于我们的function,如果function返回True就保留该元素,如果返回False就删除该元素。下面我们通过一个实例来看一下filter的用法。
定义一个函数,如果接收的字符s为空,那么返回False,如果为非空,那么返回True
>>>function=lambdas:sands.strip()
>>>iterable=['AJ',' ','Stussy','','CLOT','FCB',None]
>>>filter(function,iterable)
>>>list(filter(function,iterable))
['AJ','Stussy','CLOT','FCB']
python中自定义排序函数
Python内置的 sorted()函数可对list进行排序:
sorted([36, 5, 12, 9, 21])
输出:[5, 9, 12, 21, 36]
但 sorted()也是一个高阶函数,它可以接收一个比较函数来实现自定义排序,比较函数的定义是,传入两个待比较的元素 x, y,如果 x 应该排在 y 的前面,返回 -1,如果 x 应该排在 y 的后面,返回 1。如果 x 和 y 相等,返回 0。
因此,如果我们要实现倒序排序,只需要编写一个reversed_cmp函数:
def reversed_cmp(x, y):
if x > y:
return -1
if x < y:
return 1
return 0
这样,调用 sorted() 并传入 reversed_cmp 就可以实现倒序排序:
sorted([36, 5, 12, 9, 21], reversed_cmp)
输出:[36, 21, 12, 9, 5]
sorted()也可以对字符串进行排序,字符串默认按照ASCII大小来比较:
sorted(['bob', 'about', 'Zoo', 'Credit'])
输出:['Credit', 'Zoo', 'about', 'bob']
'Zoo'排在'about'之前是因为'Z'的ASCII码比'a'小。
示例:
对字符串排序时,有时候忽略大小写排序更符合习惯。请利用sorted()高阶函数,实现忽略大小写排序的算法。
输入:['bob', 'about', 'Zoo', 'Credit']
输出:['about', 'bob', 'Credit', 'Zoo']
走火入魔
走火(fn.py)
也许看了上面的 FP 写法,你还是觉得挺啰嗦的,并没有达到你想象中的结果,这时候就需要呈上一款语法糖利器:fn.py!fn.py
封装了一些常用的 FP 函数及语法糖,可以大大简化你的代码!
pip install fn
首先从刚刚的闭包开始,我们可以用更加 FP 的方法来解决这一问题,称为柯里化,简单来说就是允许接受多个参数的函数可以分次执行,每次只接受一个参数:
在 FP 中数据通常被看作是一段数据流在一串函数的管道中传递,因此上面的reduce和filter其实可以合并:
reduce(lambda x, y: x + y[1][1], filter(lambda x: x[0] == x[1][0], zip(student.ans, quize)), 0)
虽然更简略了,但是这样会大大降低代码的可读性(这也是 FP 容易遭受批评的一点),为此 fn 提供了更高级的函数操作工具:
入魔(Hy)
如果你觉得上面的代码已经足够魔性到看起来不像是 Python 语言了,然而一旦接受了这样的语法设定感觉也还挺不错的。如果你兴冲冲地拿去给 Lisp 或 Haskell 程序员看,则一定会被无情地鄙视,于是你痛定思痛下定决心继续挖掘 Python 函数式编程的奥妙,那么恭喜你,组织欢迎你的加入:Hail Hydra!
哦不对,说漏了,是Hi Hy!
Hy 是基于 Python 的 Lisp 方言,可以与 Python 代码进行完美互嵌(如果你更偏好 PyPy,同样也有类似的Pixie),
除此之外你也可以把它当做一门独立的语言来看待,它有自己的解释器,可以当做独立的脚本语言来使用:
pip install git+https://github.com/hylang/hy.git
首先来看一下它的基本用法,和 Python 一样,安装完之后可以通过 hy 命令进入 REPL 环境:
或者当做命令行脚本运行:
#! /usr/bin/env hy
(print "I was going to code in Python syntax, but then I got Hy.")
保存为 awesome.hy:
chmod +x awesome.hy
./awesome.hy
接下来继续以上面的问题为例,首先可以直接从 Python 代码中导入:
如果觉得不放心,还可以直接调用最开始定义的方法将结果进行比较:
;; 假设最上面的 normal 方法保存在 fun.py 文件中
(import fun)
(.normal fun students quize)
还有很多包括视频我就不一一截图了,需要这些资料的可以先关注小编,转发评论,私信小编回复006、008即可领取资料。诚信小编!!!
网友评论