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

很多人都在谈论函数式编程(Functional Programming),只是很多人站在不同的角度看到的是完全不一样的风景。坚持实用主义的 Python 老司机们对待 FP 的态度应该更加包容,虽然他们不相信银弹,但冥冥中似乎能感觉到 FP 暗合了 Python 教义(The Zen of 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 模式啦!

走火(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从入门到走火入魔_Python函数式编程:从入门到走火入魔相关推荐

  1. python从入门到走火入魔_Python 函数式编程:从入门到走火入魔

    很多人都在谈论函数式编程(Functional Programming),只是很多人站在不同的角度看到的是完全不一样的风景.坚持实用主义的 Python 老司机们对待 FP 的态度应该更加包容,虽然他 ...

  2. random函数用法_Python函数式编程:从入门到走火入魔

    很多人都在谈论函数式编程(Functional Programming),只是很多人站在不同的角度看到的是完全不一样的风景.坚持实用主义的 Python 老司机们对待 FP 的态度应该更加包容,虽然他 ...

  3. Python的函数式编程--从入门到⎡放弃⎦

    很早以前就听说过了函数式编程,印象中是一种很晦涩难懂的编程模式,但却一直没有去进行了解. 恰好这周组内的周会轮到我主持,一时也没想到要分享什么.灵光一闪,就选定函数式编程这个主题吧,反正组里的同事都没 ...

  4. Python中的匿名函数和函数式编程

    Python中的匿名函数和函数式编程 文章目录 Python中的匿名函数和函数式编程 一.匿名函数 匿名函数的格式: 二.函数式编程 map() filter() reduce() 区别 三.'三目运 ...

  5. Java8函数式编程语法入门

    Java8函数式编程语法入门 Java8中函数式编程语法能够精简代码. 使用Consumer作为示例,它是一个函数式接口,包含一个抽象方法accept,这个方法只有输入而无输出. 现在我们要定义一个C ...

  6. python的函数式编程_Python函数式编程-概念理解,python

    函数式编程-高级 一.函数的参数类型 1. 不可变类型参数 不可变类型参数有:整数,字典,字符串 传递不可变类型参数,不会影响参数本身. 代码: a = 100 print(f"函数外边a的 ...

  7. python编写函数_python函数式编程

    函数式编程是使用一系列函数去解决问题,按照一般编程思维,面对问题时我们的思考方式是"怎么干",而函数函数式编程的思考方式是我要"干什么". 至于函数式编程的特点 ...

  8. python函数的作用降低编程复杂度_Python函数式编程

    lambda 本文将介绍Python中函数式编程的特性.在对函数式编程的概念有了了解后,本文会介绍iterators和generators等语言特性,还有itertools和functools等相关的 ...

  9. python内置高阶函数求导_Python——函数式编程、高阶函数和内置函数,及

    Python--函数式编程.高阶函数及内置函数 函数式编程 一.不可变数据:不用变量保存状态不修改变量 二.第一类对象:函数即"变量" 1.函数名可以当做参数传递 2.返回值可以是 ...

最新文章

  1. 小程序音频播放报10001 解决方案 errCode:10001, errMsg:errCode:602,err:error,not found param
  2. ACCP7.0-S2-复习自测-15测试分析
  3. IBM db2安装好了以后,启动不了服务
  4. 第二周项目2-就拿胖子说事
  5. multipart/form-data和application/x-www-form-urlencoded的区别
  6. 使用Redis分布式锁处理并发,解决超卖问题
  7. 腾讯的电商,在东南亚击败了阿里巴巴
  8. 中国科学院大学计算机金智,金智-中国科学院大学-UCAS
  9. C语言实现一个随机测试加减乘除,编写程序:C语言实现一个随堂测试,能进行加减乘除运算...
  10. 【下载源码】在线生成网页缩略图.超越Snap.com:WebSnap Beta 1.1 发布。感谢博客园的“萧寒”重写的底层。开源。...
  11. sql server web管理软件
  12. 【Elasticsearch】es 报错 index has not yet rolled over with that alias
  13. 从头推导与实现 BP 网络
  14. php 用户之间通信,PHP,javascript,ajax-2位用户之间的通信
  15. 记事本如何运行python代码_记事本写代码怎么运行
  16. CAD绘图软件_常用指令
  17. ProE/Creo免费插件 MCADEx Tools 5.0
  18. 计算机视觉入门到实战教程
  19. python保存路径_Python模块的正确存放位置
  20. Android使用串口打印机打印图片方法

热门文章

  1. 中国石油大学(北京)-《 网页设计与网站建设 》-答案
  2. dubbo简介与配置
  3. 2018年的总结和随想-关于技艺的进阶的思考
  4. 综合布线系统设备选型
  5. 黑马程序员——C语言学习——预处理指令、extern与static、typedef、递归
  6. 通过写一个C++程序实现,通过ROS系统的serious包打开串口并获得数据
  7. 工业机器人焊接实操工作站
  8. 蚂蚁金服六轮面试,从一个中游的公司跳槽的阿里P7,我是怎么撑过来的?
  9. java制作在线宠物店_基于jsp的宠物店-JavaEE实现宠物店 - java项目源码
  10. 中国简化航程数据记录仪(S-VDR)市场趋势报告、技术动态创新及市场预测