##缘起
定制edx的过程中,需要自己去筛选处理不少数据,大多是直接从mongo中挖出。 大部分的工作都可以抽象描述成按条件筛选出一个数据集的子集,数据集包括courses(课程对象集合),commentThread(论坛帖子),comment(帖子评论),courseInfo(课程公告),这些数据的结构都极其相似,都是类列表数据(跟操作list基本一样), 恰好前段时间一直在关注函数式,也在学习Scheme,所以自然想到用函数式风格处理list是最方便的了。
一试,果不其然。
高阶函数提高了程序的模块性。比如使用一个高阶函数来实现排序,可以使得我们使用不同的条件(函数)来排序,这就将排序条件和排序过程清楚地划分开来。很容易写函数来实现插拔。
具体使用中,比如筛选过程使用高阶函数(def course_filter)来写,之后的筛选条件都写成filter_by_xx形式:filter_by_name/filter_by_subject,清晰明了,提高代码复用性,也导致了良好的一致性,进而使可读性提高。
使用函数风格来写函数,具体的 理念/原则 可以通过搜索函数式编程找到,其中一些比较关键的地方:
- 函数是"一等公民",使用高阶函数
- 尽量不要产生"副作用"
- 函数只是返回新的值,不修改变量.
- 引用透明(确定性),任何时候只要参数相同,引用函数所得到的返回值总是相同的。
- 函数应该像管道
- 把函数当成变量来用,关注于描述问题而不是怎么实现,这样可以让代码更易读
##函数式编程的几个技术
其实在python中以下几个东西基本也可以用for(列表推导)来做,不过列表推导不是模块化的,倾向于一次性使用、
###map
1
2
3
4
|
#列表长度
name_len = map(len, ["hao", "chen", "coolshell"])
print name_len
# 输出 [3, 4, 9]
|
这样的代码是在描述要干什么,而不是怎么干
1
2
3
4
5
6
|
#首字母大写
def toUpper(item):
return item.upper()
upper_name = map(toUpper, ["hao", "chen", "coolshell"])
# 输出 ['HAO', 'CHEN', 'COOLSHELL']
|
1
2
3
4
|
#求一组数据的平方数
squares = map(lambda x: x * x, range(9))
print squares
# 输出 [0, 1, 4, 9, 16, 25, 36, 49, 64]
|
###reduce
1
2
3
|
#求和 相当于:((((1+2)+3)+4)+5) )
print reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])
# 输出 15
|
1
2
3
4
5
6
|
#数组中正数的平均值
num =[2, -5, 9, 7, -2, 5, 3, 1, 0, -3, 8]
positive_num = filter(lambda x: x>0, num)
average = reduce(lambda x,y: x+y, positive_num) / len( positive_num )
#避免了变量倒来倒去逻辑
#代码变成了在描述你要干什么,而不是怎么去干。
|
###map函数的实现(js)
1
2
3
4
5
6
7
8
|
:::text
var map = function (mappingFunction, list) {
var result = [];
forEach(list, function (item) {
result.push(mappingFunction(item));
});
return result;
};
|
###filter
1
2
3
|
nums=[1,2,3,4,5,6,7]
b=filter(lambda x:x>5, nums)
#[6,7]
|
1
2
3
4
5
6
7
|
#是否是成年人
def is_adult(age):
if age>=18:
return True
age_list=[15,16,17,18,19]
b=filter(age, age_list)
#[18,19]
|
可以作为高阶函数的参数,模块化,可插拔
##zip
zip()是Python的一个内建函数,它接受一系列可迭代的对象作为参数,将对象中对应的元素打包成一个个tuple(元组),然后返回由这些tuples组成的list(列表)
若传入参数的长度不等,则返回list的长度和参数中长度最短的对象相同。利用*号操作符,可以将list unzip(解压)
1
2
3
4
5
6
7
8
9
|
a = [1,2,3]
b = [4,5,6]
c = [4,5,6,7,8]
zipped = zip(a,b)
#[(1, 4), (2, 5), (3, 6)]
zip(a,c)
#[(1, 4), (2, 5), (3, 6)]
zip(*zipped)
#[(1, 2, 3), (4, 5, 6)]
|
###sorted和groupby
####sorted
sorted([…],key = fuc)
1
2
3
4
5
6
|
a=[1,3,2,6,-5,-4]
sorted(a, key=lambda a:abs(a), reverse = False )
#[1,2,3,4,5,6]
#reverse:注明升序还是降序,True--降序,False--升序(默认)
#fuc 经常使用lambda
|
####groupby
1
2
3
4
5
6
7
8
9
|
for i,j in groupby("abbcdddef", lambda x:x*2):
print i,[x for x in j]
aa ['a']
bb ['b', 'b']
cc ['c']
dd ['d', 'd', 'd']
ee ['e']
ff ['f']
|
注意先排序(sorted),groupby(“abbca”)中前后两个a不会连在一起。
groupby(list,func)的返回值(i,j),i是func的计算结果;j是一个迭代器,j的内容是经过func计算值相同的lst中的元素;
本质是mapreduce 所以记不住全局,只有一个交换空间,所以必须相邻
###pipeline
pipeline 管道借鉴于Unix Shell的管道操作——把若干个命令串起来(他的设哲学就是KISS – 让每个功能就做一件事,并把这件事做到极致),前面命令的输出成为后面命令的输入,如此完成一个流式计算。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#1)找出偶数。
#2)乘以3
#3)转成字符串返回
def even_filter(nums):
return filter(lambda x: x%2==0, nums)
def multiply_by_three(nums):
return map(lambda x: x*3, nums)
def convert_to_string(nums):
return map(lambda x: 'The Number: %s' % x, nums)
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
fns = [even_filter,multiply_by_three,convert_to_string]
def pipeline_func(data, fns):
return reduce(lambda a, x: x(a),
fns,
data)
#reduce有两个列表时怎么处理?
|
###高阶函数
python中,高阶函数常常写作装饰器(Decorator),可以参考之前的一篇文章
如陈浩在博客中说的:我们不用太纠结是不是我们的程序就是OO,就是functional的,我们重要的品味其中的味道
- 具体的使用 map filter groupby sorted (信息隐藏,对外True/False)
##参考资料