前言
很多人都在谈论函数式编程(Functional Programming),只是很多人站在不同的角度看到的是完全不一样的风景。坚持实用主义的 Python 老司机们对待 FP 的态度应该更加包容,虽然他们不相信银弹,但冥冥中似乎能感觉到 FP 暗合了 Python 教义(The Zen of Python)的某些思想,而且既然 Python 是一门多范式编程语言,并在很大程度上支持函数式编程,那就更没有理由拒绝它。
函数式编程源自于数学理论,它似乎也更适用于数学计算相关的场景,因此本文以一个简单的数据处理问题为例,逐步介绍 Python 函数式编程从入门到走火入魔的过程。
对Python感兴趣的话,文末领取全套最新Python学习资源,祝你一臂之力!
问题:计算 N 位同学在某份试卷的 M 道选择题上的得分(每道题目的分值不同)。
首先来生成一组用于计算的伪造数据:
复制代码# @file: data.py
import random
from collections import namedtuple
Student = namedtuple('Student', ['id', 'ans'])
N_Questions = 25
N_Students = 20
def gen_random_list(opts, n):
return [random.choice(opts) for i in range(n)]
# 问题答案 'ABCD' 随机
ANS = gen_random_list('ABCD', N_Questions)
# 题目分值 1~5 分
SCORE = gen_random_list(range(1,6), N_Questions)
QUIZE = zip(ANS, SCORE)
students = [
# 学生答案为 'ABCD*' 随机,'*' 代表未作答
Student(_id, gen_random_list('ABCD*', N_Questions))
for _id in range(1, N_Students+1)
]
print(QUIZE)
# [('A', 3), ('B', 1), ('D', 1), ...
print(students)
# [Student(id=1, ans=['C', 'B', 'A', ...
入门
首先来看常规的面向过程编程风格,我们需要遍历每个学生,然后遍历每个学生对每道题目的答案并与真实答案进行比较,然后将正确答案的分数累计:
复制代码import data
def normal(students, quize):
for student in students:
sid = student.id
score = 0
for i in range(len(quize)):
if quize[i][0] == student.ans[i]:
score += quize[i][1]
print(sid, '\t', score)
print('ID\tScore\n==================')
normal(data.students, data.quize)
"""
ID Score
==================
1 5
2 12
...
"""
如果你觉得上面的代码非常直观且合乎逻辑,那说明你已经习惯按照计算机的思维模式进行思考了。通过创建嵌套两个 for
循环来遍历所有题目答案的判断和评分,这完全是为计算机服务的思路,虽然说 Python 中的 for
循环已经比 C
语言更进了一步,通常不需要额外的状态变量来记录当前循环的次数,但有时候也不得不使用状态变量,如上例中第二个循环中比较两个列表的元素。函数式编程的一大特点就是尽量抛弃这种明显循环遍历的做法,而是把注意集中在解决问题本身,一如在现实中我们批改试卷时,只需要将两组答案并列进行比较即可:
复制代码from data import students, QUIZE
student = students[0]
# 将学生答案与正确答案合并到一起
# 然后过滤出答案一致的题目
filtered = filter(lambda x: x[0] == x[1][0], zip(student.ans, QUIZE))
print(list(filtered))
# [('A', ('A', 3)), ('D', ('D', 1)), ...]
然后再将所有正确题目的分数累加起来,即可:
复制代码from functools import reduce
reduced = reduce(lambda x, y: x + y[1][1], filtered, 0)
print(reduced)
以上是对一位学生的结果处理,接下来只需要对所有学生进行同样的处理即可:
复制代码def cal(student):
filtered = filter(lambda x: x[0] == x[1][0], zip(student.ans, QUIZE))
reduced = reduce(lambda x, y: x + y[1][1], filtered, 0)
print(student.id, '\t', reduced)
print('ID\tScore\n==================')
# 由于 Python 3 中 map 方法只是组合而不直接执行
# 需要转换成 list 才能将 cal 方法的的结果打印出来
list(map(cal, students))
"""
ID Score
==================
1 5
2 12
...
"""
上面的示例通过 zip/filter/reduce/map
等函数将数据处理的方法打包应用到数据上,实现了基本的函数式编程操作。但是如果你对函数式有更深入的了解,你就会发现上面的 cal
方法中使用了全局变量 QUIZE
,这会导致在相同输入的条件下,函数可能产生不同的输出,这是 FP 的大忌,因此需要进行整改:
复制代码def cal(quize):
def inner(student):
filtered = filter(lambda x: x[0] == x[1][0], zip(student.ans, quize))
reduced = reduce(lambda x, y: x + y[1][1], filtered, 0)
print(student.id, '\t', reduced)
return inner
map(cal(QUIZE), students)
如此借助闭包(Closure)的方法,就可以维持纯净的 FP 模式啦!
对Python感兴趣的话,文末领取全套最新Python学习资源,祝你一臂之力!
走火(fn.py)
也许看了上面的 FP 写法,你还是觉得挺啰嗦的,并没有达到你想象中的结果,这时候就需要呈上一款语法糖利器:fn.py!fn.py
封装了一些常用的 FP 函数及语法糖,可以大大简化你的代码!
复制代码pip install fn
首先从刚刚的闭包开始,我们可以用更加 FP 的方法来解决这一问题,称为柯里化,简单来说就是允许接受多个参数的函数可以分次执行,每次只接受一个参数:
复制代码from fn.func import curried
@curried
def sum5(a, b, c, d, e):
return a + b + c + d + e
sum3 = sum5(1,2)
sum4 = sum3(3,4)
print(sum4(5))
# 15
应用到上面的 cal
方法中:
复制代码from fn.func import curried
@curried
def cal(quize, student):
filtered = filter(lambda x: x[0] == x[1][0], zip(student.ans, quize))
reduced = reduce(lambda x, y: x + y[1][1], filtered, 0)
print(student.id, '\t', reduced)
map(cal(QUIZE), students)
在 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
提供了更高级的函数操作工具:
复制代码from fn import F
cal = F() >> (filter, lambda x: x[0]==x[1][0]) >> (lambda r: reduce(_+_[1][1], r, 0))
# 计算一名学生的成绩
print(cal(zip(student.ans, QUIZE)))
# 然后组合一下
@curried
def output(quize, student):
cal = F() >> (filter, lambda x: x[0]==x[1][0]) >> (lambda r: reduce(_+_[1][1], r, 0))
print(student.id, '\t', cal(zip(student.ans, quize)))
map(output(QUIZE), students)
入魔(Hy)
如果你觉得上面的代码已经足够魔性到看起来不像是 Python 语言了,然而一旦接受了这样的语法设定感觉也还挺不错的。如果你兴冲冲地拿去给 Lisp 或 Haskell 程序员看,则一定会被无情地鄙视😂,于是你痛定思痛下定决心继续挖掘 Python 函数式编程的奥妙,那么恭喜你,组织欢迎你的加入:Hi Hy`!
Hy 是基于 Python 的 Lisp 方言,可以与 Python 代码进行完美互嵌(如果你更偏好 PyPy,同样也有类似的Pixie),除此之外你也可以把它当做一门独立的语言来看待,它有自己的解释器,可以当做独立的脚本语言来使用:
复制代码pip install git+https://github.com/hylang/hy.git
首先来看一下它的基本用法,和 Python 一样,安装完之后可以通过 hy
命令进入 REPL 环境:
复制代码=> (print "Hy!")
Hy!
=> (defn salutationsnm [name] (print (+ "Hy " name "!")))
=> (salutationsnm "YourName")
Hy YourName!
或者当做命令行脚本运行:
复制代码#! /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 代码中导入:
复制代码(import data)
;; 用于 Debug 的自定义宏
;; 将可迭代对象转化成列表后打印
(defmacro printlst [it]
`(print (list ~it)))
(setv students data.students)
(setv quize data.QUIZE)
(defn cal [quize]
(fn [student]
(print student.id
(reduce
(fn [x y] (+ x (last (last y))))
(filter
(fn [x] (= (first x) (first (last x))))
(zip student.ans quize))
0
)
)
)
)
(printl (map (cal quize) students))
如果觉得不放心,还可以直接调用最开始定义的方法将结果进行比较:
复制代码;; 假设最上面的 normal 方法保存在 fun.py 文件中
(import fun)
(.normal fun students quize)
总结
以一个简单的数据处理问题为例,我们经历了 Python 函数式编程从开始尝试到“走火入魔”的整个过程。也许你还是觉得不够过瘾,想要尝试更纯粹的 FP 体验,那么 Haskell 将是你最好的选择。FP 将数据看做数据流在不同函数间传递,省去不必要的中间变量,保证函数的纯粹性…等等这些思想在数据处理过程中还是非常有帮助的(Python 在这一领域的竞争对手 R 语言本身在语法设计上就更多地受到 Lisp 语言的影响,虽然看起来语法也比较奇怪,但这也是它比较适合用于数据处理及统计分析的原因之一)。
以上就是今天的全部内容分享,觉得有用的话欢迎点赞收藏哦!
Python经验分享
学好 Python 不论是用于就业还是做副业赚钱都不错,而且学好Python还能契合未来发展趋势——人工智能、机器学习、深度学习等。
小编是一名Python开发工程师,自己整理了一套最新的Python系统学习教程,包括从基础的python脚本到web开发、爬虫、数据分析、数据可视化、机器学习等。如果你也喜欢编程,想通过学习Python转行、做副业或者提升工作效率,这份【最新全套Python学习资料】 一定对你有用!
小编为对Python感兴趣的小伙伴准备了以下籽料 !
对于0基础小白入门:
如果你是零基础小白,想快速入门Python是可以考虑培训的!
- 学习时间相对较短,学习内容更全面更集中
- 可以找到适合自己的学习方案
包括:Python激活码+安装包、Python web开发,Python爬虫,Python数据分析,人工智能、机器学习、Python量化交易等学习教程。带你从零基础系统性的学好Python!
一、Python所有方向的学习路线
Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
二、学习软件
工欲善其事必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。
三、入门学习视频
我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。
四、实战案例
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
五、面试资料
我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
最新全套【Python入门到进阶资料 & 实战源码 &安装工具】(安全链接,放心点击)
我已经上传至CSDN官方,如果需要可以扫描下方官方二维码免费获取【保证100%免费】
*今天的分享就到这里,喜欢且对你有所帮助的话,记得点赞关注哦~下回见 !