很多人都在谈论函数式编程(Functional Programming),只是很多人站在不同的角度看到的是完全不一样的风景。坚持实用主义的 Python 老司机们对待 FP 的态度应该更加包容,虽然他们不相信银弹,但冥冥中似乎能感觉到 FP 暗合了 Python 教义(The Zen of Python)的某些思想,而且既然 Python 是一门多范式编程语言,并在很大程度上支持函数式编程,那就更没有理由拒绝它。

函数式编程源自于数学理论,它似乎也更适用于数学计算相关的场景,因此本文以一个简单的数据处理问题为例,逐步介绍 Python 函数式编程从入门到走火入魔的过程。

问题:计算 N 位同学在某份试卷的 M 道选择题上的得分(每道题目的分值不同)。

首先来生成一组用于计算的伪造数据:

# @file: data.py
import random
from collections import namedtupleStudent = namedtuple('Student', ['id', 'ans'])N_Questions = 25
N_Students = 20def 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.idscore = 0for 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, QUIZEstudent = 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 reducereduced = 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 模式啦!

走火(fn.py)

也许看了上面的 FP 写法,你还是觉得挺啰嗦的,并没有达到你想象中的结果,这时候就需要呈上一款语法糖利器:!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 + esum3 = 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 中数据通常被看作是一段数据流在一串函数的管道中传递,因此上面的reducefilter其实可以合并:

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 Fcal = 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入坑函数:从入门到走火入魔,你懂得相关推荐

  1. python中factor函数_Python入门-函数

    函数 在维基百科上函数式这样描述的: 函数在数学中为两集合间的一种对应关系:输入值集合中的每项元素皆能对应唯一一项输出值集合中的元素. 此处的函数区别于我们数学上的函数,在编程世界中,函数(Funct ...

  2. 零基28岁自学python入坑ing

    2013年,大学自考本科毕业,随意找份工作,在武汉养活自己: 2014年8月,转销售,煎熬1年,小稳定:到现在2018年8月,整整4年,除了手法娴熟以外,薪资无大幅上涨,并且武汉房价翻破了天,单身催婚 ...

  3. python入坑指南_Rust入坑指南:万物初始

    有没有同学记得我们一起挖了多少个坑?嗯-其实我自己也不记得了,今天我们再来挖一个特殊的坑,这个坑可以说是挖到根源了--元编程. 元编程是编程领域的一个重要概念,它允许程序将代码作为数据,在运行时对代码 ...

  4. Python入坑之哄女朋友开心小技巧

    前言 人生苦短,我用Python.这句话证明现在Python有多火.大约2年前我就开始接触了Python,因工作需求要自学Python ,后续的SDK打包脚本就是用Python编写的.对于Python ...

  5. 医学图像分割-入坑准备(入门)

    综述 写在前面 入门 背景知识 - PyTorch - 医学图像 - 深度学习方法 - 评估方法 写在前面 博主目前(至2021-4)研究 脑部 MRI 图像(Brain MRI),医学图像分割工作或 ...

  6. python web-python web入坑指南

    原标题:python web入坑指南 Invest regularly in your knowledge portfolio. Make learning a habit. 自学python web ...

  7. Python 小入坑

    python入坑 学习的是python3 一些信息 官网 https://www.python.org/ 官方文档 https://docs.python.org/3/ github https:// ...

  8. python 随机_python1到3秒随机延时入坑python 心情舒畅

    一开始只是看到一篇微信上的推送,讲的是用python做爬虫,爬取教务网上的成绩计算绩点,刚好做到一个需要做爬虫的项目,就看了看,觉得python很好玩,而且好像代码量比较少的样子(没错就是想偷懒... ...

  9. python函数中把列表(list)当参数时的入坑与出坑

    在Python函数中,传递的参数如果默认有一个为 列表(list),那么就要注意了,此处有坑. 入坑 挖坑 def f(x,li=[]):for i in range(x):li.append(i*i ...

最新文章

  1. Hulu直播服务难点解析(一):系统需求
  2. [论文摘录] Classification of SOA Contract Specification Languages(ICWS, 2008), 第二部分
  3. jvm性能调优实战 - 38System.gcy引发的惨案
  4. 【摄影测量原理】第四章:解析空中三角测量
  5. 获取屏幕的宽高 android,Android获取屏幕宽高的方法
  6. [Python] Ubuntu 16.04 上安装 python3.7 和 pip 并配置虚拟环境
  7. win10覆盖安装修复_联想Y460AT老本安装win7+10双系统
  8. 分析网站速度和性能的最佳工具
  9. c/c++成长之捷径
  10. Python获取国内股票数据下载数据api接口
  11. user guide for Coverity Wizard
  12. LAMMPS生成粗糙表面的in文件脚本(可调节微结构高、长和宽)
  13. 推荐个不错的 Word 全文翻译和压缩工具!
  14. BLE蓝牙的配对过程浅析
  15. 输入两个正整数m和n,求其最大公约数及最小公倍数
  16. 时序逻辑电路的设计(一) -- 模10的计数器电路(附Multisim)
  17. 随机生成小球(HTML5)
  18. unity 条目换位效果_Unity AI主题博客条目
  19. DataGrid 嵌套应用
  20. win7让笔记本的自带键盘失灵

热门文章

  1. C语言学习历程——C语言发展史以及--Hello World 程序
  2. 解决MyEclipse过期(到期提醒)问题
  3. by 到期提醒 30天
  4. 《随笔二十六》—— “ 【More Effective C++】 提炼总结一 ”
  5. 淘淘商城——Redis持久化方案
  6. 《炬丰科技-半导体工艺》氮化硅对硅和二氧化硅的高选择性蚀刻
  7. PB混淆加密大师(PB Obfuscator)产品路线PPT
  8. Bootstrap 响应式实用工具——visible-xs、visible-sm、hidden-xs、hidden-sm等
  9. 汇编书目征集(ZT)
  10. Go 区块链 Input Data 解析