Python函数式介绍一 - 高阶函数
Python函数式介绍二 - 链式调用
上一篇文章中我们发现我们的代码越来越长了,而且都挤在一行,代码越长,越不易读。括号一层嵌一层,真的容易绕晕。我一直觉得代码要写给人看,一直追求代码即注释这种程度的简单。
那有什么办法来简化这个问题?答案在标题,链式调用。我们仿照C#LINQ的链式接口。直接上代码,这分代码是我自己写的,很简单。有兴趣可以自己研究。里面封装了我觉得比较重要的几个高阶函数。如果不够的话也可以简单地再封装一下。如下,
#文件名FT.py
from functools import reduce
from collections import Iterator
from itertools import chain,groupby,product
class From:
src=None
def __init__(self,src):
self.src=src
def toList(self):
return list(self.src)
def toSet(self):
return set(self.src)
def toTuple(self):
return tuple(self.src)
def getSource(self):
return self.src
def map(self,func):
return From(map(func,self.src))
def filter(self,predicate):
return From(filter(predicate,self.src))
def reduce(self,func,identity=None):
if identity is None:
return reduce(func,self.src)
else:
return reduce(func,self.src,identity)
def chain(self):
return From(chain.from_iterable(self.src))
def groupby(self,func=None):
return From(map(lambda it:(it[0],list(it[1])),groupby(self.src,func)))
def product(self,tag):
return From(product(self.src,tag))
def all(self,predicate):
return all(map(lambda it:predicate(it),self.src))
def any(self,predicate):
return any(map(lambda it:predicate(it),self.src))
def first(self,predicate=None):
if predicate is None:
if isinstance(self.src,Iterator):
return next(self.src)
return next(iter(self.src))
else :
return next(filter(predicate,self.src))
def firstOrNone(self,predicate=None):
try:
if predicate is None:
if isinstance(self.src,Iterator):
return next(self.src)
return next(iter(self.src))
else :
return next(filter(predicate,self.src))
except StopIteration:
return None
以上代码我写了一个From
类,后面的代码会反反复复地用到这个类。
复习map/filter/reduce
例子1 ,过滤出列表中的偶数,再乘2
from FT import From
print(From((1,2,3,4))
.filter(lambda it:it % 2==0)
.map(lambda it:it*2).toList())
'''
结果:
[4,8]
'''
例子2,过滤出列表中地偶数,结果乘2,最后再求和。
from FT import From
print(From((1,2,3,4))
.filter(lambda it:it % 2==0)
.map(lambda it:it*2).reduce(lambda acc,it:acc+it))
#加幺元
print(From((1,2,3,4))
.filter(lambda it:it % 2==0)
.map(lambda it:it*2).reduce(lambda acc,it:acc+it))
'''
结果:
12
12
'''
对比上篇文章,有没有觉得清晰很多,清晰地看到数据流一步一步地往下一个函数流。这个也是函数式地特点之一。python本不提供这个流式接口,没关系,我们自己造一个,而且没有花几行代码。居然全部代码都能在一篇文章中显示出来,不过这个跟python自己动态语言有关,若换成静态语言的话那应该要花多不少功夫。下次找机会我用C++实现一遍。
下面我们学多几个高阶函数,丰富我们的武器库。
groupby
分组是一个很常见的需求,需要的代码其实也不少,封装成高阶函数后那方便太多了。请看下面的例子
testgroupdata=[{"id":1,"name":"wwb"},{"id":1,"name":"wxa"},{"id":1,"name":"wxb"},{"id":2,"name":"wxc"},{"id":2,"name":"wxd"}]
这个是数据集。是我随便造的,没什么特殊意义。
例子1,根据id分组,显示分组
print(From(testgroupdata).groupby(lambda it:it['id']).toList())
'''
结果:
[
(
1,
[
{
'id': 1,
'name': 'wwb'
},
{
'id': 1,
'name': 'wxa'
},
{
'id': 1,
'name': 'wxb'
}
]
),
(
2,
[
{
'id': 2,
'name': 'wxc'
},
{
'id': 2,
'name': 'wxd'
}
]
)
]
'''
当然groupby后可以自己接其他高阶函数,如下例子
例子2,根据id分组后,过滤出分组的KEY为偶数的数据,也就是id为偶数的数据
print(From(testgroupdata).groupby(lambda it:it['id']).filter(lambda it:it[0]%2==0).toList())
'''
结果:
[
(
2,
[
{
'id': 2,
'name': 'wxc'
},
{
'id': 2,
'name': 'wxd'
}
]
)
]
'''
笛卡尔积
笛卡儿积太重要了,数据库两种表连接或join都可以解释为笛卡儿积,看下面的例子。
例子1,观察执行结果
print(From((1,2,3)).product(('a','b','c')).toList())
现在我们考虑一个图书馆管理系统,先观察以下数据集
students = [
{
"name":"wwb",
"book":[1,2,5]
},
{
"name":"wxa",
"book":[1,2,3]
},
{
"name":"wxb",
"book":[2,3,4]
}
]
books = [
{
"id":1,
"name":"C++ Primer"
},
{
"id":2,
"name":"Effecitve C++"
},
{
"id":3,
"name":"语文"
},{
"id":4,
"name":"数学"
},
{
"id":5,
"name":"英语"
}
]
students
是图书馆借了数的同学,一个人可以借多本书,其中book
记录的是图书的id;books
是图书馆里面的书。现在可以看例子了。
例子2,求学生具体借了什么书
print(From(students).map(lambda s:
{
"name":s["name"],
"book":From(s["book"]).product(books).filter(lambda it:it[0]==it[1]["id"])
.map(lambda it:it[1]["name"]).toList()
}).toList())
'''
结果:
[
{
'name': 'wwb',
'book': [
'C++ Primer',
'Effecitve C++',
'英语'
]
},
{
'name': 'wxa',
'book': [
'C++ Primer',
'Effecitve C++',
'语文'
]
},
{
'name': 'wxb',
'book': [
'Effecitve C++',
'语文',
'数学'
]
}
]
'''
first/firstOrNone
这两个函数简单,就是找出符合条件的第一条数据。我常常需要这样的需求。直接看例子
#找出名字为wwb的同学,找不到的话则会抛出异常
print(From(students).first(lambda it:it["name"]=="wwb"))
#找出名字为wxx的同学,找不到的话返回None
print(From(students).firstOrNone(lambda it:it["name"]=="wxx"))
'''
结果:
{'name': 'wwb', 'book': [1, 2, 5]}
None
'''
first当找不到满足条件的数据会抛异常,而firstOrNone则会返回None。很简单。
chain
今天解释最后一个函数,chain。这个是一个非常重要的函数。我自己经常叫它扁平,用来变平数据。如
((a,b,c),(d,e,f),(g,h,i)) => (a,b,c,d,e,f,g,h,i)
经过chain后,你会发现括号少了一层,原来'尖'的数据现在变'平'了,因为少了一层括号。看例子
print(From(((1,2,3),(4,5,6),(1,2))).chain().toList())
'''
结果:
[1, 2, 3, 4, 5, 6, 1, 2]
'''
回到上一篇文章统计选修数学的同学的平均分。数据集如下
students = [
{
"name":"wwb",
"sex":"1",
"course":[
{
"name":"Math",
"score":90
},
{
"name":"English",
"score":80
}
]
},
{
"name":"wxa",
"sex":"1",
"course":[
{
"name":"Music",
"score":90
},
{
"name":"English",
"score":80
}
]
},
{
"name":"wxb",
"sex":"1",
"course":[
{
"name":"Math",
"score":92
},
{
"name":"Music",
"score":80
}
]
},
]
求选修数学同学的平均分
studentmaths = From(students).map(lambda s: From(s["course"]).map(lambda c:
{
"name":s["name"],
"sex":s["sex"],
"course":c["name"],
"score":c["score"]
}).toList()).chain().filter(lambda it:it["course"]=="Math").toList()
#先打印出来看看
print(From(studentmaths).reduce(lambda acc,s:acc+s["score"],0)/len(studentmaths))
'''
结果:
[{'score': 90, 'name': 'wwb', 'sex': '1', 'course': 'Math'}, {'score': 92, 'name': 'wxb', 'sex': '1', 'course': 'Math'}]
91.0
'''
总结
高阶函数和链式调用终于讲完了,有没有发现这些例子都好简洁,基本上多复杂的需求都只用一条链子,一直连击。而且不失可读性。高阶函数需要练习才会熟悉,像sql语句一样,既简单又复杂。预告下篇文章讲组合,就用我一年前写的玩具模板引擎为例子,200行左右。
测试代码
同样我自己把测试代码贴出来吧,当然需要和文章开篇的FT.py放在一起才可以执行。
from FT import From
print(From((1,2,3,4))
.filter(lambda it:it % 2==0)
.map(lambda it:it*2).toList())
print(From((1,2,3,4))
.filter(lambda it:it % 2==0)
.map(lambda it:it*2).reduce(lambda acc,it:acc+it))
print(From((1,2,3,4))
.filter(lambda it:it % 2==0)
.map(lambda it:it*2).reduce(lambda acc,it:acc+it,0))
testgroupdata=[{"id":1,"name":"wwb"},{"id":1,"name":"wxa"},{"id":1,"name":"wxb"},{"id":2,"name":"wxc"},{"id":2,"name":"wxd"}]
print(From(testgroupdata).groupby(lambda it:it['id']).toList())
print(From(testgroupdata).groupby(lambda it:it['id']).filter(lambda it:it[0]%2==0).toList())
#笛卡尔积
print(From((1,2,3)).product(('a','b','c')).toList())
students = [
{
"name":"wwb",
"book":[1,2,5]
},
{
"name":"wxa",
"book":[1,2,3]
},
{
"name":"wxb",
"book":[2,3,4]
}
]
books = [
{
"id":1,
"name":"C++ Primer"
},
{
"id":2,
"name":"Effecitve C++"
},
{
"id":3,
"name":"语文"
},{
"id":4,
"name":"数学"
},
{
"id":5,
"name":"英语"
}
]
print(From(students).map(lambda s:
{
"name":s["name"],
"book":From(s["book"]).product(books).filter(lambda it:it[0]==it[1]["id"])
.map(lambda it:it[1]["name"]).toList()
}).toList())
#只找一个
print(From(students).first(lambda it:it["name"]=="wwb"))
print(From(students).firstOrNone(lambda it:it["name"]=="wxx"))
#chain是一个很重要的函数
# 目的 ((a,b,c),(d,e,f),(g,h,i)) => (a,b,c,d,e,f,g,h,i),去掉一层括号
print(From(((1,2,3),(4,5,6),(1,2))).chain().toList())
#回到上次的例子
students = [
{
"name":"wwb",
"sex":"1",
"course":[
{
"name":"Math",
"score":90
},
{
"name":"English",
"score":80
}
]
},
{
"name":"wxa",
"sex":"1",
"course":[
{
"name":"Music",
"score":90
},
{
"name":"English",
"score":80
}
]
},
{
"name":"wxb",
"sex":"1",
"course":[
{
"name":"Math",
"score":92
},
{
"name":"Music",
"score":80
}
]
},
]
#求选修数学同学的平均分
print(From(students).map(lambda s: From(s["course"]).map(lambda c:
{
"name":s["name"],
"sex":s["sex"],
"course":c["name"],
"score":c["score"]
}).toList()).chain().toList())
studentmaths = From(students).map(lambda s: From(s["course"]).map(lambda c:
{
"name":s["name"],
"sex":s["sex"],
"course":c["name"],
"score":c["score"]
}).toList()).chain().filter(lambda it:it["course"]=="Math").toList()
print(studentmaths)
print(From(studentmaths).reduce(lambda acc,s:acc+s["score"],0)/len(studentmaths))
网友评论